Ondrej Vencovsky
Ondrej Vencovsky

Reputation: 3508

JWT based authorization .NET core 2.0 web api. Authorize by policy not working

I cannot get working authorize by policy ([Authorize(Policy = "AdminAccess")]) in my .NET Core 2.0 web api application. I use JWT based authentication and It almost works. Yes, almost. I have ValuesController that has 3 methods:

[Route("api/values")]
GetValues()

[Authorize]
[Route("api/secretvalues")]
GetSecretValues()

[Authorize(Policy = "AdminAccess")]
[Route("api/adminsecretvalues")]
GetAdminSecretValues()

I'm able to get values from unprotected method api/values. When I try to reach api/secretvalues, protected by [Authorize] I got 401 error that's OK. Then I can call api/token in my TokenController, I receive the token and if I use it in the next call to api/secretvalues everything works well, I can see the protected data. But the third method doesn't work. I allways get Unauthorized. I'm missing some little piece in the mozaic of .NET Core 2.0 JWT, but I don't know what is it.

Here's my code:

Startup.cs

namespace JwtWebApplication
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.Configure<TokenAuthenticationSettings>(Configuration.GetSection("TokenAuthentication"));

            string key = Configuration.GetSection("TokenAuthentication:Key").Value;
            var securityKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(key));

            var tokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = securityKey,
                ValidateIssuer = true,
                ValidIssuer = Configuration.GetSection("TokenAuthentication:Issuer").Value,
                ValidateAudience = true,
                ValidAudience = Configuration.GetSection("TokenAuthentication:Audience").Value,
                ValidateLifetime = true,
                ClockSkew = TimeSpan.Zero
            };

            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(options => {
                    options.RequireHttpsMetadata = false;
                    options.SaveToken = true;
                    options.TokenValidationParameters = tokenValidationParameters;
                });

            services.AddAuthorization(options =>
            {
                options.DefaultPolicy = new AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme).RequireAuthenticatedUser().Build();
                options.AddPolicy("AdminAccess", policy => policy.RequireClaim("role", "admin"));
            });

            services.AddCors(options =>
            {
                options.AddPolicy("CorsPolicy",
                    builder => builder.AllowAnyOrigin()
                        .AllowAnyMethod()
                        .AllowAnyHeader()
                        .AllowCredentials());
            });

            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseAuthentication();            // JWT Authentication
            app.UseMvc();
        }
    }
}

TokenController.cs

namespace JwtWebApplication.Controllers
{
    public class TokenController : Controller
    {
        private readonly IConfiguration configuration;

        public TokenController(IConfiguration configuration)
        {
            this.configuration = configuration;
        }

        [HttpPost]
        [Route("api/token")]
        public IActionResult GetToken([FromBody] TokenRequest tokenRequest)
        {
            if (string.IsNullOrWhiteSpace(tokenRequest.Username) || (tokenRequest.Username != tokenRequest.Password))
            {
                return BadRequest();
            }

            return new ObjectResult(GenerateToken(tokenRequest.Username));
        }

        private string GenerateToken(string username)
        {
            var claims = new Claim[]
            {
                new Claim(ClaimTypes.Name, username),
                new Claim(JwtRegisteredClaimNames.Nbf, new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds().ToString()),
                new Claim(JwtRegisteredClaimNames.Exp, new DateTimeOffset(DateTime.Now.AddDays(1)).ToUnixTimeSeconds().ToString()),
                new Claim(ClaimTypes.Role, "admin"),
                new Claim("role", "admin")
            };

            string issuer = this.configuration.GetSection("TokenAuthentication:Issuer").Value;
            string audience = this.configuration.GetSection("TokenAuthentication:Audience").Value;
            string key = this.configuration.GetSection("TokenAuthentication:Key").Value;

            SymmetricSecurityKey securityKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(key));
            SigningCredentials signingCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

            var token = new JwtSecurityToken(issuer, audience, claims, null, null, signingCredentials);

            return new JwtSecurityTokenHandler().WriteToken(token);
        }
    }
}

ValuesController.cs

namespace JwtWebApplication.Controllers
{
    public class ValuesController : Controller
    {
        [AllowAnonymous]
        [HttpGet]
        [Route("api/values")]
        public IEnumerable<string> GetValues()
        {
            return new string[] { "Value 1", "Value 2", "Value 3" };
        }

        [Authorize]
        [HttpGet]
        [Route("api/secretvalues")]
        public IEnumerable<string> GetSecretValues()
        {
            return new string[] { "Secret Value 1", "Secret Value 2", "Secret Value 3" };
        }

        [Authorize(Policy = "AdminAccess")]
        [HttpGet]
        [Route("api/adminsecretvalues")]
        public IEnumerable<string> GetAdminSecretValues()
        {
            return new string[] { "Admin Secret Value 1", "Admin Secret Value 2", "Admin Secret Value 3" };
        }
    }
}

I'm adding the Claim new Claim(ClaimTypes.Role, "admin") to the token and I thought it's enough but obviously it's not. Like if it's not unpacked during authorization process. But if I debug the HttpContext.User object in ValuesController, I can see the claims are there, but it simply doesn't work. Perhaps I don't understand something but I don'k know what is it.

Upvotes: 0

Views: 1108

Answers (1)

Syed Farabi
Syed Farabi

Reputation: 175

Had the same issue and then found the solution, In case anyone else is having the same issue.

services.AddAuthorization(options => { options.AddPolicy("AdminAccess", policy => { policy.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme); policy.RequireAuthenticatedUser(); policy.Requirements.Add(new MinimumAgeRequirement()); }); });

When adding policy, define AuthenticationScheme as well.

Here is the link where you can learn more

https://learn.microsoft.com/en-us/aspnet/core/security/authorization/limitingidentitybyscheme?tabs=aspnetcore2x

Upvotes: 3

Related Questions