using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Options; using System.Globalization; using System.Security.Claims; using Microsoft.AspNetCore.Mvc; var builder = WebApplication.CreateBuilder(); builder.Services.AddAuthentication().AddJwtBearer(); builder.Services.AddAuthorization(); builder.Services.AddControllers(); builder.Services.AddSingleton<IAuthorizationPolicyProvider, MinimumAgePolicyProvider>(); builder.Services.AddSingleton<IAuthorizationHandler, MinimumAgeAuthorizationHandler>(); builder.Services.AddControllers(); var app = builder.Build(); app.MapControllers(); app.Run(); [ApiController] [Route("api/[controller]")] public class GreetingsController : Controller { [MinimumAgeAuthorize(16)] [HttpGet("hello")] public string Hello() => $"Hello {(HttpContext.User.Identity?.Name ?? "world")}!"; } class MinimumAgeAuthorizeAttribute : AuthorizeAttribute { const string POLICY_PREFIX = "MinimumAge"; public MinimumAgeAuthorizeAttribute(int age) => Age = age; // Get or set the Age property by manipulating the underlying Policy property public int Age { get { if (int.TryParse(Policy.Substring(POLICY_PREFIX.Length), out var age)) { return age; } return default(int); } set { Policy = $"{POLICY_PREFIX}{value.ToString()}"; } } } class MinimumAgePolicyProvider : IAuthorizationPolicyProvider { const string POLICY_PREFIX = "MinimumAge"; public DefaultAuthorizationPolicyProvider FallbackPolicyProvider { get; } public MinimumAgePolicyProvider(IOptions<AuthorizationOptions> options) { // ASP.NET Core only uses one authorization policy provider, so if the custom implementation // doesn't handle all policies (including default policies, etc.) it should fall back to an // alternate provider. // // In this sample, a default authorization policy provider (constructed with options from the // dependency injection container) is used if this custom provider isn't able to handle a given // policy name. // // If a custom policy provider is able to handle all expected policy names then, of course, this // fallback pattern is unnecessary. FallbackPolicyProvider = new DefaultAuthorizationPolicyProvider(options); } public Task<AuthorizationPolicy> GetDefaultPolicyAsync() => FallbackPolicyProvider.GetDefaultPolicyAsync(); public Task<AuthorizationPolicy> GetFallbackPolicyAsync() => FallbackPolicyProvider.GetFallbackPolicyAsync(); // Policies are looked up by string name, so expect 'parameters' (like age) // to be embedded in the policy names. This is abstracted away from developers // by the more strongly-typed attributes derived from AuthorizeAttribute // (like [MinimumAgeAuthorize()] in this sample) public Task<AuthorizationPolicy> GetPolicyAsync(string policyName) { if (policyName.StartsWith(POLICY_PREFIX, StringComparison.OrdinalIgnoreCase) && int.TryParse(policyName.Substring(POLICY_PREFIX.Length), out var age)) { var policy = new AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme); policy.AddRequirements(new MinimumAgeRequirement(age)); return Task.FromResult(policy.Build()); } return Task.FromResult<AuthorizationPolicy>(null); } } class MinimumAgeRequirement : IAuthorizationRequirement { public int Age { get; private set; } public MinimumAgeRequirement(int age) { Age = age; } } class MinimumAgeAuthorizationHandler : AuthorizationHandler<MinimumAgeRequirement> { private readonly ILogger<MinimumAgeAuthorizationHandler> _logger; public MinimumAgeAuthorizationHandler(ILogger<MinimumAgeAuthorizationHandler> logger) { _logger = logger; } // Check whether a given MinimumAgeRequirement is satisfied or not for a particular context protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, MinimumAgeRequirement requirement) { // Log as a warning so that it's very clear in sample output which authorization policies // (and requirements/handlers) are in use _logger.LogWarning("Evaluating authorization requirement for age >= {age}", requirement.Age); // Check the user's age var dateOfBirthClaim = context.User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth); if (dateOfBirthClaim != null) { // If the user has a date of birth claim, check their age var dateOfBirth = Convert.ToDateTime(dateOfBirthClaim.Value, CultureInfo.InvariantCulture); var age = DateTime.Now.Year - dateOfBirth.Year; if (dateOfBirth > DateTime.Now.AddYears(-age)) { // Adjust age if the user hasn't had a birthday yet this year age--; } // If the user meets the age criterion, mark the authorization requirement succeeded if (age >= requirement.Age) { _logger.LogInformation("Minimum age authorization requirement {age} satisfied", requirement.Age); context.Succeed(requirement); } else { _logger.LogInformation("Current user's DateOfBirth claim ({dateOfBirth}) does not satisfy the minimum age authorization requirement {age}", dateOfBirthClaim.Value, requirement.Age); } } else { _logger.LogInformation("No DateOfBirth claim present"); } return Task.CompletedTask; } }