Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET Core JWT mapping role claims to ClaimsIdentity

I want to protect ASP.NET Core Web API using JWT. Additionally, I would like to have an option of using roles from tokens payload directly in controller actions attributes.

Now, while I did find it out how to use it with Policies:

Authorize(Policy="CheckIfUserIsOfRoleX") ControllerAction()... 

I would like better to have an option to use something usual like:

Authorize(Role="RoleX") 

where Role would be automatically mapped from JWT payload.

{     name: "somename",     roles: ["RoleX", "RoleY", "RoleZ"] } 

So, what is the easiest way to accomplish this in ASP.NET Core? Is there a way to get this working automatically through some settings/mappings (if so, where to set it?) or should I, after token is validated, intercept generation of ClaimsIdentity and add roles claims manually (if so, where/how to do that?)?

like image 286
dee zg Avatar asked Feb 04 '17 05:02

dee zg


People also ask

What is ClaimsIdentity in ASP.NET Core?

In . NET Core, the ClaimsIdentity class represents a user in your application. It helps describe who they are and helps manage the list of claims which describe what they can do.

How do I apply a claim in .NET core?

Claim based authorization checks are declarative - the developer embeds them within their code, against a controller or an action within a controller, specifying claims which the current user must possess, and optionally the value the claim must hold to access the requested resource.


2 Answers

You need get valid claims when generating JWT. Here is example code:

Login logic:

[HttpPost] [AllowAnonymous] public async Task<IActionResult> Login([FromBody] ApplicationUser applicationUser) {     var result = await _signInManager.PasswordSignInAsync(applicationUser.UserName, applicationUser.Password, true, false);     if(result.Succeeded) {         var user = await _userManager.FindByNameAsync(applicationUser.UserName);          // Get valid claims and pass them into JWT         var claims = await GetValidClaims(user);          // Create the JWT security token and encode it.         var jwt = new JwtSecurityToken(             issuer: _jwtOptions.Issuer,             audience: _jwtOptions.Audience,             claims: claims,             notBefore: _jwtOptions.NotBefore,             expires: _jwtOptions.Expiration,             signingCredentials: _jwtOptions.SigningCredentials);         //...     } else {         throw new ApiException('Wrong username or password', 403);     } } 

Get user claims based UserRoles, RoleClaims and UserClaims tables (ASP.NET Identity):

private async Task<List<Claim>> GetValidClaims(ApplicationUser user) {     IdentityOptions _options = new IdentityOptions();     var claims = new List<Claim>         {             new Claim(JwtRegisteredClaimNames.Sub, user.UserName),             new Claim(JwtRegisteredClaimNames.Jti, await _jwtOptions.JtiGenerator()),             new Claim(JwtRegisteredClaimNames.Iat, ToUnixEpochDate(_jwtOptions.IssuedAt).ToString(), ClaimValueTypes.Integer64),             new Claim(_options.ClaimsIdentity.UserIdClaimType, user.Id.ToString()),             new Claim(_options.ClaimsIdentity.UserNameClaimType, user.UserName)         };     var userClaims = await _userManager.GetClaimsAsync(user);     var userRoles = await _userManager.GetRolesAsync(user);     claims.AddRange(userClaims);     foreach (var userRole in userRoles)     {         claims.Add(new Claim(ClaimTypes.Role, userRole));         var role = await _roleManager.FindByNameAsync(userRole);         if(role != null)         {             var roleClaims = await _roleManager.GetClaimsAsync(role);             foreach(Claim roleClaim in roleClaims)             {                 claims.Add(roleClaim);             }         }     }     return claims; } 

In Startup.cs please add needed policies into authorization:

void ConfigureServices(IServiceCollection service) {    services.AddAuthorization(options =>     {         // Here I stored necessary permissions/roles in a constant         foreach (var prop in typeof(ClaimPermission).GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy))         {             options.AddPolicy(prop.GetValue(null).ToString(), policy => policy.RequireClaim(ClaimType.Permission, prop.GetValue(null).ToString()));         }     }); } 

ClaimPermission:

public static class ClaimPermission {     public const string         CanAddNewService = "Tự thêm dịch vụ",         CanCancelCustomerServices = "Hủy dịch vụ khách gọi",         CanPrintReceiptAgain = "In lại hóa đơn",         CanImportGoods = "Quản lý tồn kho",         CanManageComputers = "Quản lý máy tính",         CanManageCoffees = "Quản lý bàn cà phê",         CanManageBillards = "Quản lý bàn billard"; } 

Use the similar snippet to get all pre-defined permissions and insert it to asp.net permission claims table:

var staffRole = await roleManager.CreateRoleIfNotExists(UserType.Staff);  foreach (var prop in typeof(ClaimPermission).GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy)) {     await roleManager.AddClaimIfNotExists(staffRole, prop.GetValue(null).ToString()); } 

I am a beginner in ASP.NET, so please let me know if you have better solutions.

And, I don't know how worst when I put all claims/permissions into JWT. Too long? Performance ? Should I store generated JWT in database and check it later for getting valid user's roles/claims?

like image 53
trinvh Avatar answered Sep 18 '22 17:09

trinvh


This is my working code! ASP.NET Core 2.0 + JWT. Adding roles to JWT token.

appsettings.json

"JwtIssuerOptions": {    "JwtKey": "4gSd0AsIoPvyD3PsXYNrP2XnVpIYCLLL",    "JwtIssuer": "http://yourdomain.com",    "JwtExpireDays": 30 } 

Startup.cs

// ===== Add Jwt Authentication ======== JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); // => remove default claims // jwt // get options var jwtAppSettingOptions = Configuration.GetSection("JwtIssuerOptions"); services     .AddAuthentication(options =>     {         options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;         options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;         options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;     })     .AddJwtBearer(cfg =>     {         cfg.RequireHttpsMetadata = false;         cfg.SaveToken = true;         cfg.TokenValidationParameters = new TokenValidationParameters         {             ValidIssuer = jwtAppSettingOptions["JwtIssuer"],             ValidAudience = jwtAppSettingOptions["JwtIssuer"],             IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtAppSettingOptions["JwtKey"])),             ClockSkew = TimeSpan.Zero // remove delay of token when expire         };     }); 

AccountController.cs

[HttpPost] [AllowAnonymous] [Produces("application/json")] public async Task<object> GetToken([FromBody] LoginViewModel model) {     var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, false, false);      if (result.Succeeded)     {         var appUser = _userManager.Users.SingleOrDefault(r => r.Email == model.Email);         return await GenerateJwtTokenAsync(model.Email, appUser);     }      throw new ApplicationException("INVALID_LOGIN_ATTEMPT"); }  // create token private async Task<object> GenerateJwtTokenAsync(string email, ApplicationUser user) {     var claims = new List<Claim>     {         new Claim(JwtRegisteredClaimNames.Sub, email),         new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),         new Claim(ClaimTypes.NameIdentifier, user.Id)     };      var roles = await _userManager.GetRolesAsync(user);      claims.AddRange(roles.Select(role => new Claim(ClaimsIdentity.DefaultRoleClaimType, role)));      // get options     var jwtAppSettingOptions = _configuration.GetSection("JwtIssuerOptions");      var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtAppSettingOptions["JwtKey"]));     var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);     var expires = DateTime.Now.AddDays(Convert.ToDouble(jwtAppSettingOptions["JwtExpireDays"]));      var token = new JwtSecurityToken(         jwtAppSettingOptions["JwtIssuer"],         jwtAppSettingOptions["JwtIssuer"],         claims,         expires: expires,         signingCredentials: creds     );      return new JwtSecurityTokenHandler().WriteToken(token); } 

Fiddler test GetToken method. Request:

POST https://localhost:44355/Account/GetToken HTTP/1.1 content-type: application/json Host: localhost:44355 Content-Length: 81  {     "Email":"[email protected]",     "Password":"ukj90ee",     "RememberMe":"false" } 

Debug response token https://jwt.io/#debugger-io

Payload data:

{   "sub": "[email protected]",   "jti": "520bc1de-5265-4114-aec2-b85d8c152c51",   "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier": "8df2c15f-7142-4011-9504-e73b4681fb6a",   "http://schemas.microsoft.com/ws/2008/06/identity/claims/role": "Admin",   "exp": 1529823778,   "iss": "http://yourdomain.com",   "aud": "http://yourdomain.com" } 

Role Admin is worked!

like image 23
dev-siberia Avatar answered Sep 20 '22 17:09

dev-siberia