Reputation: 357
I've successfully got JWT Token Authentication working on my API, however, I'm struggling to integrate Role claims into the authentication process.
What I want to achieve it being able to add the following to a call in my controller, and have the site automatically check the Role Claims against the authenticated user:
[Authorize(Roles = "SiteAdmin")]
This is the code that generates the JWT Token:
private string GenerateJwtToken(UserDto user)
{
// generate token that is valid for 7 days
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[] { new Claim("userAccountId", user.UserAccountId.ToString()) }),
Expires = DateTime.UtcNow.AddHours(_appSettings.JwtExpiryHours),
SigningCredentials =
new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
I've tried following this guide here (https://www.jerriepelser.com/blog/using-roles-with-the-jwt-middleware/) and changed the above function to the following:
private async Task<string> GenerateJwtToken(UserDto user)
{
// generate token that is valid for 7 days
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[] { new Claim("userAccountId", user.UserAccountId.ToString()) }),
Expires = DateTime.UtcNow.AddHours(_appSettings.JwtExpiryHours),
SigningCredentials =
new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature),
Claims = await FetchClaims(user.UserAccountId)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
private async Task<Dictionary<string, object>> FetchClaims(int userId)
{
var userPermissions = await GetUserPermissionsByUserId(userId);
var userClaims = new Dictionary<string, object>();
foreach (var userPermission in userPermissions)
{
userClaims.Add("http://schemas.microsoft.com/ws/2008/06/identity/claims/role", userPermission.PermissionName);
}
return userClaims;
}
While the above makes sense in my head in theory, I can't add more than one Role claim due to the fact that when adding a userClaim to the dictionary, it's adding a duplicate key.
I must be misunderstanding on how this implementation on this works, any help would be appreciated.
Upvotes: 1
Views: 2381
Reputation: 3636
I'm also not sure what's going on with the 'permissions', but assuming that you're getting the roles...
You can simply refactor it this way, and add the list of claims to the collection you pass to ClaimsIdentity
:
private async Task<string> GenerateJwtToken(UserDto user)
{
// generate token that is valid for 7 days
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
var claims = new List<Claim>(await FetchClaims(user.UserAccountId)) {
new Claim("userAccountId", user.UserAccountId.ToString())
};
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.UtcNow.AddHours(_appSettings.JwtExpiryHours),
SigningCredentials =
new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
private async Task<IEnumerable<Claim>> FetchClaims(int userId)
{
var userPermissions = await GetUserPermissionsByUserId(userId);
return userPermissions
.Select(x => new Claim(ClaimTypes.Role, x.PermissionName));
}
(You can also use a JwtSecurityToken
instead of SecurityTokenDescriptor
, if you want; that accepts an IEnumerable<Claim>
directly.)
I admittedly haven't used Roles for a good while, so let me know if it doesn't work. But from a quick googling it seems it's legit to add multiple Role claims to the token. [Edit: I tested adding multiple Role claims and authorizing against them, and it worked for me.]
Upvotes: 4