Reputation: 4594
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?
Upvotes: 1
Views: 5067
Reputation: 2414
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://learn.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.
Upvotes: 3