0

Let me give you some context: I am trying to implement custom policies in an ASP.NET Core Web API while using JWT bearer tokens.

But for some reason when trying to reach the protected routes, this error shows up:

05:02:07 INF] Authorization failed. These requirements were not met: RolesAuthorizationRequirement:User.IsInRole must be true for one of the following roles: (Sudo)
05:02:07 INF] AuthenticationScheme: Identity.Application was challenged. 05:02:07 INF] Request finished
HTTP/1.1 GET http://localhost:5259/api/auth/ - 302 0 null 6.7246ms
05:02:07 INF] Request starting HTTP/1.1 GET http://localhost:5259/Account/Login?ReturnUrl=%2Fapi%2Fauth%2F - application/json null

I understand this basically says that the user couldn't be authorized successfully.

I've checked all the relevant configuration and I can't seem to understand why it isn't working. I was even thinking maybe it was Insomnia which was not properly giving the bearer token but I've tested other projects and they do work in said matter.

I am working on a refresh/access token based authorization. To my understanding the default Token for Authorization its the bearer token within the headers. Which gets sends when you add it to Insomnia. The string itself.

I also have the refresh token as an httponly token. But I haven't tested it yet as I am still figuring out the register and login endpoints.

I've yet found why this isn't working. Let me give you some relevant code for you to understand better my setup.

This is my relevant Program.cs configuration:

public static void ConfigureAuthentication(this IServiceCollection services, IConfiguration config)
{
    services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            }).AddJwtBearer(options =>
            {
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuer = true,
                    ValidIssuer = config["JWT:Issuer"],
                    ValidateAudience = false,
                    ValidateLifetime = true,
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config["JWT:Key"] ?? throw new Exception("Security Key not found within configuration")))
                };
            });
}

public static void ConfigureAuthorization(this IServiceCollection services, IConfiguration config)
{
    services.AddAuthorization(options =>
        {
            options.AddPolicy("SudoPolicy", p => p.AddRequirements(
                new IsSudoRequirement()
            ));
            options.AddPolicy("SudoRole", p => p.RequireRole("Sudo"));
        });
}

public static void ConfigureIdentity(this IServiceCollection services)
{
    services.AddIdentity<Usuario, IdentityRole>(options =>
        {
            options.Password.RequiredLength = 6;
        }).AddEntityFrameworkStores<AppDbContext>();
}

This is my custom policy. It wasn't meant to be used but I figured I'd try implementing one and just seeing if I could make it work that way.

public class IsSudoHandler : AuthorizationHandler<IsSudoRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, IsSudoRequirement requirement)
    {
        if (context.User.HasClaim(c => c.Type == "Role" && c.Value.Contains("Sudo")))
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

public class IsSudoRequirement : IAuthorizationRequirement
{
    public IsSudoRequirement()
    {
        IsSudo = true;
    }

    public bool IsSudo { get; }
}

Now the way I am handling the return of the access token is as such:

public async Task<string> CreateAccessTokenAsync(Usuario usuario)
{
    var roles = await _userManager.GetRolesAsync(usuario);

    var claims = new List<Claim>()
        {
            new Claim(JwtRegisteredClaimNames.Sub, usuario.UserName!),
            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
            new Claim(JwtRegisteredClaimNames.Iat, DateTime.UtcNow.ToString())
        };

    claims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role)));

    var creds = new SigningCredentials(_key, SecurityAlgorithms.HmacSha256);
    var tokenDescriptor = new SecurityTokenDescriptor
        {
            Subject = new ClaimsIdentity(claims),
            Expires = DateTime.UtcNow.AddMinutes(10),
            NotBefore = DateTime.UtcNow.AddSeconds(5),
            Issuer = _config["JWT:Issuer"],
            SigningCredentials = creds
        };

    var tokenHandler = new JwtSecurityTokenHandler();
    var token = tokenHandler.CreateToken(tokenDescriptor);

    return tokenHandler.WriteToken(token);
}

This is how I am testing this specific token:

Access Token in Insomnia

And this is how I try to test it:

[HttpGet]
[Authorize("SudoRole")]
public IActionResult Test()
{
    return Ok("Poto");
}

And it throws that error. I've trying doing different policies. Using roles. Using claims. But I've yet found why it isn't working. Maybe I am doing something wrong not doing something. I've yet found why it is giving me said error.

This is the full repo in case you are curious: Github Repository

Any guidance, resource or advice with how to fix this issue as well as with the architecture and the way the program is structured would be highly appreciated.

Thank you for your time!

1
  • Where did you register your handler in the services collection? Something like builder.Services.AddSingleton<IAuthorizationHandler, IsSudoHandler>(); Commented Feb 21 at 16:45

1 Answer 1

1

As mentioned in the comment, you didn't say if you registered your Handler.

I would suggest next changes:

Delete your existing IsSudoHandler, IsSudoRequirement and ConfigureAuthorization. Then do next changes:

[HttpGet]
[Authorize(Policy = AuthRolesPolicy.SudoRole)]
public IActionResult Test()
{
    return Ok("Poto");
}

For that you would need AuthRolesPolicy.cs

public static class AuthRolesPolicy
{
    public const string SudoRole = "Sudo";

    public static readonly string[] SudoLevel = { SudoRole };
}

RoleRequirement class that would be used for Bearer.

public class RoleRequirement : IAuthorizationRequirement
{
    public string[] Roles { get; }

    public RoleRequirement(string[] roles)
    {
        Roles = roles;
    }
}

Actual handler MyAuthorizationHandler.cs

public class MyAuthorizationHandler : AuthorizationHandler<RoleRequirement>, IAuthorizationRequirement
{
    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, RoleRequirement requirement)
    {
        try
        {
            // Read user identities from authentication step
            // Here you can use some extensions to read role directly from context, but main logic should be clear
            if (context.User.HasClaim(c => c.Type == "Role" && requirement.Roles.Contains(c.Value)))
            {
                context.Succeed(requirement);
            }
            // ....
            // return Task.CompletedTask;
        }
        catch (Exception ex)
        {                
        }
        
        context.Fail();
        return;
    }
}

And the last one is update Program.cs

...
builder.Services.AddScoped<IAuthorizationHandler, MyAuthorizationHandler>();
...
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy(AuthRolesPolicy.SudoRole, policy => policy.Requirements.Add(new RoleRequirement(AuthRolesPolicy.SudoLevel)));
});
...

I've not looked into your repo in depth, but please do research about .gitignore and use it always for your git repos.

Sign up to request clarification or add additional context in comments.

1 Comment

Appreciate your input, friend. I did do your advice and changed the way I handled custom policies but the error kept coming. In order to fix it I had to do as in this post: wildermuth.com/2018/04/10/… The issue was coming from the fact that I had the Identity Service configured after setting up my Authentication configuration.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.