Search code examples
.net-coreblazorblazor-client-side

How do I restrict page access on user properties


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.


Solution

  • 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;
            }
        }