Reputation: 3508
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
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
Upvotes: 3