I have implemented a simple JWT authentication system in my code. When using the [Authorize] attribute and then analysing the user claim inside the controller code, I can see that the user has the role "Admin" However, if I change the Authorize to [Authorize(Roles = "Admin")] I get a 403 returned. Note that I'm testing these methods through Postman.
Token generation:
[AllowAnonymous]
[HttpPost]
[Route("Login")]
//[HttpPost("Login/{username}/{password}")]
public IActionResult Login(string username, string password)
{
var authRepo = new AuthenticationRepository();
if (authRepo.Login(username, password))
{
var ua = new UserAttributes { Email = username, Role = "Admin" };
var token = GenerateToken(ua);
return Ok(token);
}
return NotFound("User not found");
}
private string GenerateToken(UserAttributes userAttributes)
{
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
var claims = new[]
{
new Claim(ClaimTypes.Name, userAttributes.Email),
new Claim(ClaimTypes.Role, userAttributes.Role)
};
var token = new JwtSecurityToken(_config["Jwt:Issuer"],
_config["Jwt:Audience"],
claims,
expires: DateTime.Now.AddMinutes(Convert.ToDouble(_config["Jwt:Expires"])),
signingCredentials: credentials);
return new JwtSecurityTokenHandler().WriteToken(token);
}
Authorized code:
[HttpGet]
[Route("TestRole")]
[Authorize]
public IActionResult AdminEndPoint()
{
var currentUser = GetCurrentUser();
if (currentUser != null)
return Ok($"Hi {currentUser.Email} you are an {currentUser.Role}");
else
return NotFound();
}
private UserAttributes? GetCurrentUser()
{
var identity = HttpContext.User.Identity as ClaimsIdentity;
if (identity != null)
{
var userClaims = identity.Claims;
return new UserAttributes
{
Email = userClaims.FirstOrDefault(x => x.Type == ClaimTypes.Name)?.Value,
Role = userClaims.FirstOrDefault(x => x.Type == ClaimTypes.Role)?.Value
};
}
return null;
}
Note that this works, but when I change to [Authorize(Roles = 'Admin')] it fails.
For completeness, this is my startup:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options => {
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = false,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
RoleClaimType = "role",
NameClaimType = "name",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]))
};
options.Events = new JwtBearerEvents()
{
OnMessageReceived = msg =>
{
var token = msg?.Request.Headers.Authorization.ToString();
string path = msg?.Request.Path ?? "";
if (!string.IsNullOrEmpty(token))
{
Console.WriteLine("Access token");
Console.WriteLine($"URL: {path}");
Console.WriteLine($"Token: {token}\r\n");
}
else
{
Console.WriteLine("Access token");
Console.WriteLine("URL: " + path);
Console.WriteLine("Token: No access token provided\r\n");
}
return Task.CompletedTask;
},
OnTokenValidated = ctx =>
{
Console.WriteLine();
Console.WriteLine("Claims from the access token");
if (ctx?.Principal != null)
{
foreach (var claim in ctx.Principal.Claims)
{
Console.WriteLine($"{claim.Type} - {claim.Value}");
}
}
Console.WriteLine();
return Task.CompletedTask;
},
OnAuthenticationFailed = aut =>
{
Console.WriteLine("Exceptoin during authentication");
Console.WriteLine(aut.Exception);
Console.WriteLine("");
return Task.CompletedTask;
}
};
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
Microsoft and OpenID Connect have different opinion on what the name/role claims should be and because of that ,you need to tell AddJwtBearer what the name of the role and name claim is, using:
For more details, I wrote a blog post about it:
Debugging JwtBearer Claim Problems in ASP.NET Core