ASP.NET Core Jwt Mapping Role Claims to Claimsidentity

JWT token role claim translated wrong by DOTNET

Asp.net core can automatic mapping some claims, You can add this configuration in Program.cs(Net 6) to avoid this

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

There are more details in this document, You can refer to it.

JWT authorization with roles in Identity Core

You don't need to use IdentityUser and identity database in your case, you are using JWT. Create your User model with defined Roles property and simple persist it in the database. Like:

public class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Role Role { get; set; }
}

public enum Role
{
Viewer,
Developer,
Manager
}

the token:

var user = // ...
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(your_seccret_key);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.Name, user.FirstName),
new Claim(ClaimTypes.Name, user.LastName),
new Claim(ClaimTypes.Role, user.Role)
}),
Expires = DateTime.UtcNow.AddDays(1),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key),SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
user.Token = tokenHandler.WriteToken(token);

controller method:

[Authorize(Roles = Role.Developer)]
[HttpGet("GetSomethingForAuthorizedOnly")]
public async Task<object> GetSomething()
{
// .... todo
}

Asp.Net Core jwt token is transformed after authentication

It has in fact been understood as ClaimTypes.Email, however the string returned by this property is http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress (source).

The token is not transformed, unless specifically done so, rather parsed and understood as ClaimTypes.Email with the actual token not modified.

How to populate JWT claims dynamically so policies can be used in controllers?

I have done something similar, I check the user has the required permission on each request. Mine is done based on Features, but you can change it a bit to be based on Roles.

I have a User with 1 Role, and then Role has N Features.

For this I use created my own AuthorizationHandler as follows. AuthorizationHandlers are at the start of the pipeline, so it will go through all AuthorizationHandlers before hitting the controller endpoint. Offical Docs here

'IFeaturesProvider': is just my Business layer to retrieve features from DB.

ICustomUserContext: Is a wrapper around HttpContextAccessor.HttpContext.User (I will post it at the end of answer for clarity.)

public class FeatureRequirement : IAuthorizationRequirement
{
public string FeatureName { get; set; }

public FeatureRequirement(string featureName)
{
FeatureName = featureName;
}
}

/// <summary>
/// Authorisation based on Feature.
/// A user is assigned a Role, a role can have N Features.
/// If the user is not assigned the Feature required to access the Controller Action this will throw an Authorisation Error -
/// </summary>
public class FeatureAuthorizationHandler : AuthorizationHandler<FeatureRequirement>
{
private readonly IFeaturesProvider _featuresProvider;
private readonly ICustomUserContext _userContext;

public FeatureAuthorizationHandler([FromServices] IFeaturesProvider featuresProvider,
[FromServices] ICustomUserContext userContext)
{
_featuresProvider = featuresProvider;

_userContext = userContext;
}

protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, FeatureRequirement requirement)
{
// Authentication of user failed - Means Token expired or Token incorrect BUT Token exists in Header
if (!context.User.Identity.IsAuthenticated)
{
return Task.FromResult(0);
}

// get the logged in user id and user type
var userId = _userContext.UserId;
var userType = _userContext.UserType;

// get features assigned to currently logged in user
var userFeatures = _featuresProvider.GetFeaturesByUserId(userId, userType);

// check if user has the required feature assigned to it's Role
if (userFeatures.Select(s => s.Name).Contains(requirement.FeatureName))
context.Succeed(requirement);

return Task.FromResult(0);
}
}

Then in Startup.cs you want to regiseter all your feature/roles as a permission.

services.AddAuthorization(options =>
{
// load all features from DB
var features = rolesProvider.GetAllFeatures(validationContainer);

// Add policy foreach feature in DB
foreach (var feature in features)
{
options.AddPolicy(feature.Name, policy => policy.Requirements.Add(new FeatureRequirement(feature.Name)));
}
});

Also in Startup, register your Depency Injection for the AuthorizationHandler

services.AddScoped<IAuthorizationHandler, FeatureAuthorizationHandler>();

Then in controller you can just do:

[Authorize(Policy = "View Asset Driver")]
public class AssetDriversController : BaseController
{

where "View Asset Driver" is the name of one of my features.

OR

[Authorize(Policy = "View Asset Driver")]
public async Task<IActionResult> GetAssetDrivers(int companyId, int assetId)

Here is the ICustomUserContext:

   public interface ICustomUserContext
{
ClaimsPrincipal CurrentUser { get; }
int UserId { get; }
UserTypeEnum UserType { get; }
int ResellerId { get; }
int CompanyId { get; }
int DriverId { get; }
}

public class CustomUserContextAdapter : ICustomUserContext
{
private readonly IHttpContextAccessor _accessor;

public CustomUserContextAdapter(IHttpContextAccessor accessor)
{
_accessor = accessor;
}

public ClaimsPrincipal CurrentUser => _accessor.HttpContext.User;
public int UserId => CurrentUser != null ? CurrentUser.GetUserIdInternal() : 0;
public UserTypeEnum UserType => CurrentUser != null ? CurrentUser.GetUserTypeInternal() : UserTypeEnum.User;
public int ResellerId => CurrentUser != null ? CurrentUser.GetResellerIdInternal() : 0;
public int CompanyId => CurrentUser != null ? CurrentUser.GetCompanyIdInternal() : 0;
public int DriverId => CurrentUser != null ? CurrentUser.GetDriverIdInternal() : 0;
}

.NET [Authorize] - Use JWT Body Path Other than Role

First solution that comes to mind

You could play it via policies and use roles in the background. Here is an example. In your controller:

[Authorize(Policy = "Foo")]
public Whatever() {}

And then you register your policy like this:

services.AddAuthorization(options =>
{
options.AddPolicy("Foo", policy => policy.RequireClaim("www.bar.com/role", new[] { "Foo" }));
}

I.e. your "Foo" policy requires a user to have the value "Foo" for the claim "www.bar.com/role".

Digging deeper into the source

In fact if you add the Authorize attribute with a role, not much more happens in the background as in the Policy example above. .net creates an authorization policy with a RolesAuthorizationRequirement, and this requirement checks the roles by calling context.User.IsInRole, which, in turn calls HasClaim(_identities[i].RoleClaimType, role) on the ClaimsIdentity. RoleClaimType is set in .net ClaimsIdentity to "roles/" by default.

So another way would probably be intercepting the creation of the ClaimsIdentity and calling it with another constructor which has a "roleType" parameter, which allows overriding of the default role claim name.

A probably better solution

So I've looked for the ClaimsIdentity and found this docu. It seems that you can get to it via token validation parameters:

services.AddAuthentication()
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
RoleClaimType = "www.bar.com/role"
}
})


Related Topics



Leave a reply



Submit