Matt G
Matt G

Reputation: 632

ASP.Net Core WebAPI Authorization Policy for User or Admin

I have a controller that returns data about users. I want to set the authorization such that an admin can access this controller and retrieve data for any user, and a non-admin user can access the controller and retrieve data for themselves.

I've ruled out using [Authorize (Roles = "Admin")] because this means users can't get their own data. So I've inserted the following logic into the controller action:

var userId = _httpContextAccessor.HttpContext.User.FindFirst(ClaimTypes.Name).Value;
var roles = _httpContextAccessor.HttpContext.User.FindAll(ClaimTypes.Role);

var query = roles.Select(r => r.Value).Contains("Admin");

Customer customer =await _context.Customers.FindAsync(id);

if (!(customer.EmailAddress == userId || query))
 return Unauthorized();

This is roughly equivalent to this Stack Overflow answer, but for ASP.Net Core rather than MVC.

My question is, is there a way to do this with an Authorization Policy? Adding the RequireRole check is straightforward and covered in the MS Documentation as well as countless blogs, but I couldn't find or figure out a way to use a policy to check that the data the user is trying to access is their own.

I'm sure this isn't an uncommon requirement, is there a way to do this, or is what I'm currently doing OK? The only other approach I could think of was to have two separate endpoints, but both options seem inelegant.

Upvotes: 0

Views: 1562

Answers (1)

Nan Yu
Nan Yu

Reputation: 27528

The policy is for authorization , but either Admin or A normal user can access the controller , they are all authorized .

That is your custom logic to determine which data should be returned , that is nothing related to authorization . If you insist on using policy , you can put the logic to handler but that is nothing change when logic is in controller :

public class CustomerHandler : AuthorizationHandler<CustomerRequirement>
{
    IHttpContextAccessor _httpContextAccessor = null;

    public CustomerHandler(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
                                               CustomerRequirement requirement)
    {

        HttpContext httpContext = _httpContextAccessor.HttpContext;

        //your logic 

        httpContext.Items["message"] = "ownData";

        context.Succeed(requirement);
        return Task.CompletedTask;
    }
}

And read in controller so that you can know whether read his own data or all users' data :

var message = HttpContext.Items["message"];

In my option ,set two endpoints/function in your web api , one for admin , one for user is the clean way . In addition , that is your client app's responsibility to determine that current user wants to return his own data or all user's data . That seems not quite correct to send request to web api and let api to determine by logic . Webapi should include the clean functions/endpoint to map each request from client .

Upvotes: 1

Related Questions