I'm building this app and so far I've implemented the Identity API.
Login/Register works, which both return a user with a token. Using [Authorize]
on my controllers works when I pass the JWT token in Postman.
However, when adding [Authorize(Roles = "Administrator")]
, I get a 403 Response, despite checking that the role "Administrator" is indeed assigned to the user.
This is the method in the UserController:
`[Authorize(Roles = ("Administrator"))]
[HttpGet]
public async Task<ActionResult<IEnumerable<AppUser>>> GetUsers()
{
return await _userManager.Users.ToListAsync();
}`
And this is the TokenService class:
`public class TokenService
{
public readonly IConfiguration _config;
public TokenService(IConfiguration config)
{
_config = config;
}
public string CreateToken(AppUser user)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.UserName),
new Claim(ClaimTypes.NameIdentifier, user.Id),
new Claim(ClaimTypes.Email, user.Email)
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["TokenKey"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.Now.AddDays(7),
SigningCredentials = creds
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
}`
And this is the Program.cs class `private static async Task Main(string[] args) { var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers(opt =>
{
// Every endpoint requires Authentication, except for those that we add [AllowAnonnymous] to
var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
opt.Filters.Add(new AuthorizeFilter(policy));
});
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddDbContext<ApplicationDbContext>(option =>
option.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddControllersWithViews();
builder.Services.AddScoped<ICompany, CompanyService>();
builder.Services.AddScoped<IJobPosition, JobPositionService>();
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddScoped<IApplicationService, ApplicationService>();
builder.Services.AddScoped<IGetUser, UserService>();
builder.Services.AddScoped<IDocuments, DocumentService>();
builder.Services.AddScoped<IResults, ResultService>();
builder.Services.AddControllersWithViews();
builder.Services.AddDefaultIdentity<AppUser>().AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddIdentityServices(builder.Configuration);
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("RequireAdministratorRole",
policy => policy.RequireRole("Administrator"));
});
var app = builder.Build();
var scopeFactory = app.Services.GetRequiredService<IServiceScopeFactory>();
using (var scope = scopeFactory.CreateScope())
{
var services = scope.ServiceProvider;
try
{
var context = services.GetRequiredService<ApplicationDbContext>();
var userManager = services.GetRequiredService<UserManager<AppUser>>();
var roleManager = services.GetRequiredService<RoleManager<IdentityRole>>();
await context.Database.MigrateAsync();
await Seed.SeedData(context, userManager, roleManager);
} catch(Exception ex) {
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occured during migration");
}
}
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
await app.RunAsync();
}`
What am I missing here?
CodePudding user response:
Add the roles to the claims, something like this:
public string CreateToken(AppUser user, string[] roles)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, user.Id),
new Claim(ClaimTypes.Name, user.UserName),
new Claim(ClaimTypes.Email, user.Email)
};
foreach (var userRole in roles)
{
claims.Add(new Claim(ClaimTypes.Role, userRole));
}
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["TokenKey"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.Now.AddDays(7),
SigningCredentials = creds
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
Also, if I remember correctly, NameIdentifier claim has to be first in order to have Identity work with signalr, in case you are planning to use it.