I am new to webapi and mvc and I am struggling to find a best practice for handling authorizations dynamically based on roles and ownership of the resource. For example an account page that should allow employee admins, employee call center or the owning client to Get, Post, Put or Delete account information. So an admin and call center employee should be able to Get, Post, Put or Delete any request for any userid, but a client should only be able to perform these actions on resources owned by them.

For example Tom is UserID 10 and Jerry is UserID 20.

/api/Account/10 should be accessible by any admin, call center or Tom. Jerry should be kicked out. /api/Account/20 should be accessible by any admin, call center or Jerry. Tom should be kicked out.

In webforms the typical solution is to just check if the user is a client and verify their id against the request. (I know AuthorizeAttribute is not in webforms, but showing as an example of what it would covert to in webapi/mvc.)

    [Authorize(Roles = "Administrator, CallCenter, Client")]
    public string Get(int userID)
    {
        if (Thread.CurrentPrincipal.IsInRole("Client") && Thread.CurrentPrincipal.Identity.userID != userID)
        { 
            //Kick them out of here.
        }
        return "value";
    }

This will work, but it seems like the check for ownership should happen in a single location before it reaches the controller and should be reusable throughout an application. I am guessing the best place would either be a custom AuthorizationFilterAttribute or a custom AuthorizeAttribute and maybe create a new role ClientOwner.

    [Authorize(Roles = "Administrator, CallCenter, ClientOwner")]
    public string Get(int userID)
    {
        return "value";
    }

Custom AuthorizeAttribute

    public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
    {
        //If user is already authenticated don't bother checking the header for credentials
        if (Thread.CurrentPrincipal.Identity.IsAuthenticated) { return; }

        var authHeader = actionContext.Request.Headers.Authorization;

        if (authHeader != null)
        {
            if (authHeader.Scheme.Equals("basic", StringComparison.OrdinalIgnoreCase) &&
                !String.IsNullOrWhiteSpace(authHeader.Parameter))
            {
                var credArray = GetCredentials(authHeader);
                var userName = credArray[0];
                var password = credArray[1];

                //Add Authentication
                if (true)
                {
                    var currentPrincipal = new GenericPrincipal(new GenericIdentity(userName), null);
                    var user = GetUser(userName);

                    foreach (var claim in user.Cliams)
                    {
                        currentPrincipal.Identities.FirstOrDefault().AddClaim(new Claim(ClaimTypes.Role, claim);
                    }
                    //**************Not sure best way to get UserID below from url.***********************
                    if (user.userTypeID = UserTypeID.Client && user.userID == UserID)
                    {
                        currentPrincipal.Identities.FirstOrDefault().AddClaim(new Claim(ClaimTypes.Role, "ClientOwner"));
                    }
                    Thread.CurrentPrincipal = currentPrincipal;
                    return;
                }
            }
        }

        HandleUnauthorizedRequest(actionContext);
    }}

Can someone point me in the right direction as to the best place to handle the authorization of the individual user? Should this still be done in the controller or should I move it to a custom AuthorizationFilterAttribute or a custom AuthorizationAttribute or is there somewhere else this should be handled? If the proper place is in a custom attribute, then what is the best way to get the userID and should I create a new role like the example above or should I do something different?

This is a common scenario and I am very surprised that I have struggled to find examples of the above scenario. This leads me to believe that either everyone is doing the check in the controller or there is another term I am not aware of so I am not getting good google results.

有帮助吗?

解决方案

I think you may be getting authorization and permissions confused. "Dynamic authorization" isn't something you ever do.

Authorization is the act of verifying an author.

  1. Request claims it is being sent from Alice.
  2. Request presents a password or authorization token that proves the requester is Alice.
  3. Server verifies that the password or authorization token matches its records for Alice.

Permissions are the business logic that specifies who can do what in your system.

  1. Request is already authorized, and we know it came from Alice.
  2. Alice is requesting to delete an important resource.
  3. Is Alice an administrator? If not, tell her she can't do that because she doesn't have permission. (403 Forbidden)

The built-in [Authorize] attribute lets you optionally specify Roles that are permitted to access a resource. That option to specify permissions as part of authorization is slightly misplaced, in my opinion.

My advice would be to leave authorization as purely the process of verifying the author of a request. The BasicAuthHttpModule described here is close to what you want already.

Non-trivial permissions logic needs to be handled inside of your action body. Here's an example:

//Some authorization logic:
//  Only let a request enter this action if the author of
//  the request has been verified
[Authorize]
[HttpDelete]
[Route("resource/{id}")]
public IHttpActionResult Delete(Guid id)
{
    var resourceOwner = GetResourceOwner(id);

    //Some permissions logic:
    //  Only allow deletion of the resource if the
    //  user is both an admin and the owner.
    if (!User.IsInRole("admin") || User.Identity.Name != resourceOwner)
    {
        return StatusCode(HttpStatusCode.Forbidden);
    }

    DeleteResource(id);
    return StatusCode(HttpStatusCode.NoContent);
}

In this example, it would be difficult to convey the permissions logic as an attribute on the action, because the portion of the permissions that compares the current user to the resource owner can only be evaluated after you have actually gotten the resource owner info from your backend storage device.

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top