I have a blazor page I need to restrict access to by age. I am using hosted webassembly. I would like to use [Authorize] attribute but I can not figure out how to make it work on a calculation from date of birth. I'm storing the date of birth in DateTimeOffset format.
Firstly you need to make the Dob a claim. I am not sure if you should use JwtClaimTypes.BirthDate
as your datetime format is not the same as what is expected here.
public class CustomUserClaimsPrincipalFactory
: UserClaimsPrincipalFactory<ApplicationUser, IdentityRole>
{
public CustomUserClaimsPrincipalFactory(
UserManager<ApplicationUser> userManager,
RoleManager<IdentityRole> roleManager,
IOptions<IdentityOptions> optionsAccessor)
: base(userManager, roleManager, optionsAccessor)
{ }
public override async Task<ClaimsPrincipal> CreateAsync(ApplicationUser user)
{
ClaimsPrincipal principal = await base.CreateAsync(user);
var identity = (ClaimsIdentity)principal.Identity;
var claims = new List<Claim>
{
new Claim(JwtClaimTypes.BirthDate, JsonSerializer.Serialize(user.DoB))
};
...
identity.AddClaims(claims);
return principal;
}
}
To use this add this line to startup.cs on the server code
services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddClaimsPrincipalFactory<CustomUserClaimsPrincipalFactory>();
You also need to make sure the claim gets added to the JWT so the client can see it.
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
{
const string OpenId = "openid";
options.IdentityResources[OpenId].UserClaims.Add(JwtClaimTypes.BirthDate);
options.ApiResources.Single().UserClaims.Add(JwtClaimTypes.BirthDate);
});
You then have to set up a Policy in Program.cs
for the client
builder.Services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();
builder.Services.AddAuthorizationCore(options => options.AddPolicy("AtLeast18", policy => policy.Requirements.Add(new MinimumAgeRequirement(18)));
Then you can use the [Authorize] attribute on your page.
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize(Policy="AtLeast18")]
@page "/adults"
<h3>Adults Only</h3>
Policy Classes:
public class MinimumAgeRequirement : IAuthorizationRequirement
{
public int MinimumAge { get; }
public MinimumAgeRequirement(int minimumAge)
{
MinimumAge = minimumAge;
}
}
public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
public MinimumAgeHandler(IDateTimeBroker dateTimeBroker)
{
DateTimeBroker = dateTimeBroker;
}
protected IDateTimeBroker DateTimeBroker { get; }
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, MinimumAgeRequirement requirement)
{
if (!context.User.HasClaim(c => c.Type == JwtClaimTypes.BirthDate))
{
return Task.CompletedTask;
}
var claimValue = context.User.FindFirst(c => c.Type == JwtClaimTypes.BirthDate).Value;
var dob = JsonSerializer.Deserialize<DateTimeOffset?>(claimValue);
if (!dob.HasValue)
{
return Task.CompletedTask;
}
var dateOfBirth = dob.Value;
int calculatedAge = DateTimeBroker.GetDateTime().Date.Year - dateOfBirth.Year;
if (dateOfBirth > DateTimeBroker.GetDateTime().AddYears(-calculatedAge))
{
calculatedAge--;
}
if (calculatedAge >= requirement.MinimumAge)
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}