[SOLVED] How to set AuthorizationHandlerContext.User

Issue

I created an authorization based on this guide, and at one point in the AuthorizationHandler, AuthorizationHandlerContext.User is used:

using System.Security.Claims;
using AuthorizationPoliciesSample.Policies.Requirements;
using Microsoft.AspNetCore.Authorization;

namespace AuthorizationPoliciesSample.Policies.Handlers;

public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context, MinimumAgeRequirement requirement)
    {
        var dateOfBirthClaim = context.User.FindFirst(
            c => c.Type == ClaimTypes.DateOfBirth && c.Issuer == "http://contoso.com");

        if (dateOfBirthClaim is null)
        {
            return Task.CompletedTask;
        }

        var dateOfBirth = Convert.ToDateTime(dateOfBirthClaim.Value);
        int calculatedAge = DateTime.Today.Year - dateOfBirth.Year;
        if (dateOfBirth > DateTime.Today.AddYears(-calculatedAge))
        {
            calculatedAge--;
        }

        if (calculatedAge >= requirement.MinimumAge)
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

It’s nice that this value is not null, but where does it come from? How do I fill in my data to make this authorization useful? I’ve read the the "Security and Identity" pages and I’m really not clear on that.

Solution

To hook into the authorization header, an implementation of AuthenticationHandler is needed.

public class UserAuthenticationHandler : AuthenticationHandler<UserAuthenticationOptions>
    {
        public const string Schema = "Basic";
        private const string HeaderAuthorization = "Authorization";

        public UserAuthenticationHandler(
            IOptionsMonitor<UserAuthenticationOptions> options,
            ILoggerFactory logger,
            UrlEncoder encoder,
            ISystemClock clock)
            : base(options, logger, encoder, clock)
        {
        }

        protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
        {
            if (!Request.Headers.ContainsKey(HeaderAuthorization))
            {
                return AuthenticateResult.Fail("Unauthorized - no \"" + HeaderAuthorization + "\" header found.");
            }

            // get the value of the authorization header
            string authorizationHeader = Request.Headers[HeaderAuthorization];
            if (string.IsNullOrEmpty(authorizationHeader))
            {
                return AuthenticateResult.NoResult();
            }

            // snip the schema if it is present
            if (authorizationHeader.StartsWith(Schema, StringComparison.OrdinalIgnoreCase))
            {
                authorizationHeader = authorizationHeader[Schema.Length..];
            }

            // now delegate the actual validation of the string
            try
            {
                return ValidateToken(authorizationHeader.Trim());
            }
            catch (Exception ex)
            {
                return AuthenticateResult.Fail(ex.Message);
            }
        }

        protected AuthenticateResult ValidateToken(string token)
        {
            var identity = new ClaimsIdentity(new List<Claim> {new(ClaimTypes.Name, token)}, Scheme.Name);
            var principal = new GenericPrincipal(identity, Array.Empty<string>());
            var ticket = new AuthenticationTicket(principal, Scheme.Name);
            return AuthenticateResult.Success(ticket);
        }
    }

    public class UserAuthenticationOptions : AuthenticationSchemeOptions
    {
    }

And then register it in during the Startup like this:

services.AddAuthentication(UserAuthenticationHandler.Schema).AddScheme<UserAuthenticationOptions, UserAuthenticationHandler>(UserAuthenticationHandler.Schema, null);

Answered By – Steffi S.

Answer Checked By – Timothy Miller (BugsFixing Admin)

Leave a Reply

Your email address will not be published. Required fields are marked *