7

I have JWT-based claims authentication/ authorization set up in my .NET Core application, which authenticates as expected, but my policy enforcement is not acting as I would expect.

I have a requirements implementation and handler set up as follows:

public class ImpersonationRequirement : IAuthorizationRequirement
{
}

public class ImpersonationHandler : AuthorizationHandler<ImpersonationRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
        ImpersonationRequirement requirement)
    {
        if (context.User.CanImpersonate()) context.Succeed(requirement);

        return Task.CompletedTask;
    }
}

I have a helper set up like so:

public static bool CanImpersonate(
    this ClaimsPrincipal principal)
{
    var val = principal?.FindFirst(MyClaimTypes.CAN_IMPERSONATE)?.Value;
    return bool.TryParse(val, out var value) && value;
}

public class MyClaimTypes
{
    /// <summary>
    /// Boolean value indicating this user is authorized to impersonate other customer accounts.
    /// </summary>
    public const string CAN_IMPERSONATE = "cim";

    ...

    /// <summary>
    /// Actual name of the user impersonating the current user.
    /// </summary>
    public const string IMPERSONATING_USER = "imp";
}

...off of my Startup.cs, I have the policy defined:

services.AddAuthorization(options =>
{
    options.AddPolicy("Impersonator", policy => policy.Requirements.Add(new ImpersonationRequirement()));
});

...and on my controller, it's written as such:

[Produces("application/json")]
[Authorize(Policy = "Impersonator")]
public class ImpersonationController : Controller
{
    private readonly ILogger _logger;
    private readonly ITokenManagementService _tokenManagementService;
    private readonly UserManager<MyUser> _userManager;

    public ImpersonationController(ITokenManagementService tokenManagementService, ILoggerFactory loggerFactory, UserManager<MyUser> userManager)
    {
        _tokenManagementService = tokenManagementService;
        _userManager = userManager;
        _logger = loggerFactory.CreateLogger<ImpersonationController>();
    }

    [HttpPost]
    [Route("~/api/impersonation/token")]
    [ProducesResponseType(typeof(AuthenticationResponse), 200)]
    [ProducesResponseType(typeof(Exception), 500)]
    public async Task<IActionResult> Impersonate([FromBody] string userNameToImpersonate)
    {
        try
        {
            var impersonated = await _userManager.FindByNameAsync(userNameToImpersonate);
            if (impersonated == null) throw new EntityNotFoundException($"Unable to find user '{userNameToImpersonate}' in the data store.");
            var actualUserId = User.UserId();
            var token = await _tokenManagementService.GenerateJwt(impersonated.Id, actualUserId);
            var refresh = await _tokenManagementService.GenerateRefreshToken(impersonated.Id, actualUserId);
            var response = new AuthenticationResponse {AuthenticationToken = token, RefreshToken = refresh};
            return Ok(response);
        }
        catch (Exception ex)
        {
            return new OopsResult(ex);
        }
    }
}

If I run this with the AuthorizeAttribute commented out, I can take a look at the user's claims, and the "cim: true" is in the claims enumeration, but if I run it with the AuthorizeAttribute enabled, I get a 403 Forbidden error.

I tried putting a breakpoint on the line in the ImpersonationHandler:

if (context.User.CanImpersonate()) context.Succeed(requirement);

...but the debugger never stops here, so I don't know what the problem is. Can someone educate me as to what I'm doing wrong?

Jeremy Holovacs
  • 22,480
  • 33
  • 117
  • 254
  • Yup, that was it. Interesting; I'd expect a NullRefException if I missed a dependency. Good to know. If you want to make an answer, I'll accept it. – Jeremy Holovacs Apr 06 '18 at 14:46

1 Answers1

18

It seems you forgot to register your ImpersonationHandler in DI container (which is indeed easy to forget):

services.AddSingleton<IAuthorizationHandler, ImpersonationHandler>();

Asp.net resolves all such handlers from container and tries to match for specific requirement. Since no such handler is registered - nothing sets context.Succeed and whole authorization fails.

Evk
  • 98,527
  • 8
  • 141
  • 191