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.