简体   繁体   中英

How to implement Claim based authorization using Web Api Asp.Net Core?

I am working with Web API and generate JWT token on login. I have a very complex requirement regarding user roles and claims. A user can have multiple roles. A role can have multiple claims and a user can have particular claims. I am using Asp.Net Core Identity. One user can have data in AspNetUserClaims and same user can have different role and each role have claims in AspRoleClaims. I have successfully implemented the Role base authorization using web api but how can I implement claim based authorization in web API?

The one thing that I have already implemented, but don't really like is that I am storing all user claims including claims in the role in a JWT token and on each request I am extracting the claims from the JWT. But is this a best practice or not - Storing large data in JWT token?

Or what is the other way to do claim based authorization in Web API Dot Net core?

You can create custom policies like so, you can have multiple individual policies and stack them with authorize tags, or you can have one handler to deal with multiple policies, or in a policy you can have multiple requirements in the policyBuilder (I like this approach for a lot of use cases). See the info below and the microsoft link

an example for registering a custom policy:

Startup.cs

 services.AddAuthorization(authorizationOptions =>
 {
    authorizationOptions.AddPolicy(
    "MustBeBornInSummer",
    policyBuilder =>
    {
       //add any other policy requirements here too including ones by default
       //eg policyBuilder.RequireAuthenticatedUser();
        policyBuilder.AddRequirements(
            new MustBeBornInSummerRequirement()
            //, new AnotherRequirement()
            );
    });
    //only if you want to register as the default policy
    authorizationOptions.DefaultPolicy = authorizationOptions.GetPolicy("MustBeBornInSummer");
});

Then in the authorize

Tag you use

[Authorize("MustBeBornInSummer")]

You can set a default policy either which allows you to use the Authorize tag as normal

authorizationOptions.DefaultPolicy = authorizationOptions.GetPolicy("MustBeBornInSummer");

Your requirement class

public class MustBeBornInSummerRequirement : IAuthorizationRequirement
    {
        public MustBeBornInSummerRequirement ()
        {
        }
    }

Handler class

 public class MustBeBornInSummerHandler: AuthorizationHandler<MustBeBornInSummerRequirement>
    {

        public MustBeBornInSummerHandler ()
        {
            //your dependency injections
        }

        protected override Task HandleRequirementAsync(
            AuthorizationHandlerContext context, 
            MustBeBornInSummer requirement)
        {  
//Get the claim you want
            var subject = context.User.Claims.FirstOrDefault(c => c.Type == "sub")?.Value;

            var dateOfBirth = _useryRepository.GetApplicationUserProfile(subject)?.dob;


           //your logic`check user claims then do stuff, or/and check roles too
            if (!bornInSummer)
            {
                context.Fail();
                return Task.CompletedTask;
            }


            // has claims
            context.Succeed(requirement);
            return Task.CompletedTask;
        }
    }

In your configure services you need to register it:

services.AddScoped<IAuthorizationHandler, MustBeBornInSummerHandler>();

You Can have One handler for multiple requirements too see

public class PermissionHandler : IAuthorizationHandler
{
    public Task HandleAsync(AuthorizationHandlerContext context)
    {
        var pendingRequirements = context.PendingRequirements.ToList();
        foreach (var requirement in pendingRequirements)
        {
          if(requirementMustBeBornInSummer
             //Dostuff continue
          elseIf(requirementIsBlah)
        }
    }
}

How to handle claims is up to you, this is how I normally do it

var subject = context.User.Claims.FirstOrDefault(c => c.Type == "sub")?.Value;

var dateOfBirth = _useryRepository.GetApplicationUserProfile(subject)?.dob;


//My repository
public ApplicationUserProfile GetApplicationUserProfile(string subject)
{
    return _context.ApplicationUserProfiles.FirstOrDefault(a => a.Subject == subject);
}

you can also have individual policies and stack them

[Authorize(Policy = "MustBeBornInSummer")]
[Authorize(Policy = "readRole")]

https://docs.microsoft.com/en-us/aspnet/core/security/authorization/policies?view=aspnetcore-3.1

As for storing large amounts of data in a JWT, for application specific claims I usually create a table that stores claims for a user in a particular app. I then use the "sub" of the token to look up the claims the user has. This stops you needing to return really large tokens.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM