JwtSecurityTokenHandler.CreateJwtSecurityToken renaming Claim Type Names.
See original GitHub issueFrom @TobyMosque on July 30, 2018 23:16
I’m tring to secure my web tokens using Aes256CbcHmacSha512, but when i create the SecurityToken calling JwtSecurityTokenHandler.CreateToken Method (SecurityTokenDescriptor), the claims’ type names are updated.
Because of that, the role-based authorization didn’t work as expected (AuthorizationAttribute.Roles didn’t work and I can’t get the Current User).
AppSettings.Keys.cs
public static class Keys
{
public static string SigningKey { get; }
public static string DecryptionKey { get; }
public static string JwtIssuer { get; } = "https://sample.placebo.com";
public static string JwtAudience { get; } = "https://sample.placebo.com";
static Keys()
{
var rng = RandomNumberGenerator.Create();
var signingKey = new byte[64];
var decryptionKey = new byte[32];
rng.GetBytes(signingKey);
rng.GetBytes(decryptionKey);
Keys.SigningKey = Convert.ToBase64String(signingKey);
Keys.DecryptionKey = Convert.ToBase64String(decryptionKey);
}
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
...
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
var authBuilder = services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
});
authBuilder.AddJwtBearer(cfg =>
{
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
var binarySigningKey = Convert.FromBase64String(AppSettings.Keys.SigningKey);
var binaryDecryptionKey = Convert.FromBase64String(AppSettings.Keys.DecryptionKey);
cfg.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = AppSettings.Keys.JwtIssuer,
ValidAudience = AppSettings.Keys.JwtAudience,
IssuerSigningKey = new SymmetricSecurityKey(binarySigningKey),
TokenDecryptionKey = new SymmetricSecurityKey(binaryDecryptionKey),
ClockSkew = TimeSpan.Zero
};
});
...
}
AuthController.cs
[ApiController]
[Route("api/[controller]/[action]")]
public class AuthController : ControllerBase
{
private readonly UserManager<IdentityUser<Guid>> _userManager;
private readonly SignInManager<IdentityUser<Guid>> _signInManager;
public AuthController(UserManager<IdentityUser<Guid>> userManager, SignInManager<IdentityUser<Guid>> signInManager)
{
this._userManager = userManager;
this._signInManager = signInManager;
}
// GET api/values
[HttpPost]
public async Task<ActionResult> CreateAdmin([FromBody]UsuarioModel model)
{
var usuario = new IdentityUser<Guid> { Id = Guid.NewGuid(), UserName = model.Logon, Email = model.Logon };
var result = await this._userManager.CreateAsync(usuario, model.Password);
if (!result.Succeeded)
return BadRequest(result.Errors);
await this._userManager.AddToRoleAsync(usuario, "Admin");
return Ok();
}
// GET api/values/5
[HttpPost]
public async Task<ActionResult> Logon([FromBody]UsuarioModel model)
{
var user = await _userManager.FindByEmailAsync(model.Logon);
if (user == null)
return NotFound();
var result = await _signInManager.PasswordSignInAsync(user, model.Password, true, false);
if (!result.Succeeded)
return BadRequest();
var binSigning = Convert.FromBase64String(AppSettings.Keys.SigningKey);
var binEncrypting = Convert.FromBase64String(AppSettings.Keys.DecryptionKey);
var claims = new ClaimsIdentity();
claims.AddClaims(new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString())
});
var roles = await _userManager.GetRolesAsync(user);
claims.AddClaims(roles.Select(role => new Claim(ClaimTypes.Role, role)));
var keySigning = new SymmetricSecurityKey(binSigning);
var signing = new SigningCredentials(keySigning, SecurityAlgorithms.HmacSha256);
var keyEncrypting = new SymmetricSecurityKey(binEncrypting);
var encrypting = new EncryptingCredentials(keyEncrypting, SecurityAlgorithms.Aes256KW, SecurityAlgorithms.Aes256CbcHmacSha512);
var expires = DateTime.Now.AddDays(30);
var handler = new JwtSecurityTokenHandler();
var token = handler.CreateToken(new SecurityTokenDescriptor
{
Issuer = AppSettings.Keys.JwtIssuer,
Audience = AppSettings.Keys.JwtAudience,
Subject = claims,
NotBefore = DateTime.Now,
Expires = expires,
IssuedAt = DateTime.Now,
SigningCredentials = signing,
EncryptingCredentials = encrypting
});
return Ok(handler.WriteToken(token));
}
[HttpGet]
[Authorize(Roles = "Admin")]
public ActionResult IsAdmin()
{
return Ok();
}
[HttpGet]
[Authorize]
public async Task<ActionResult> GetRoles()
{
var user = await this._userManager.GetUserAsync(User);
var roles = await this._userManager.GetRolesAsync(user);
return Ok(roles);
}
}
Reproducing the problem.:
Creating the User.:
Request
curl -X POST "https://localhost:5001/api/Auth/CreateAdmin" -H "accept: application/json" -H "Content-Type: application/json-patch+json" -d "{ \"logon\": \"admin@placebo.ocom\", \"password\": \"Placebo$512\"}"
Response Headers - HttpStatus 200
access-control-allow-origin: *
content-length: 0
date: Mon, 30 Jul 2018 22:34:32 GMT
server: Kestrel
Autheticating
Request
curl -X POST "https://localhost:5001/api/Auth/Logon" -H "accept: application/json" -H "Content-Type: application/json-patch+json" -d "{ \"logon\": \"admin@placebo.ocom\", \"password\": \"Placebo$512\" }"
Response Body
"eyJhbGciOiJBMjU2S1ciLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIiwidHlwIjoiSldUIn0.V9QoFJE3HGRLIGNAMnqUwLDhoIwRkM4oBJWOdR7swnx-D80ttcWkxWLZvJlLo2mrjOOqz2w6fxr7kpOGVpxOa9sak0XirDYe.JA0T9RjdxsEdbGAhM-FoDA.JxdItRfs7OSfl96dT-rd9kuoH0FW2lnPc2Aa3uXvtJNqW0XgrG6GwoRv_biIAglhfa-1FWHwPbSnEmD8H0TmdX_Z6d1oWyRivbgy8Bjyh1DWVIRm7hq4C0sRCDWNHjQlGt0dVipkcNM48gPGpBG3DiBCheyzA9-W5gp_mipqTgzZ1qAh1HYdHu0K4A4lZqCjjlQr0UJ2FBVzcj4FkbaH0id4t9aK1vYwak8QU3i3ZS6ZOZ9HIeWTXgUcV4j6lEDyYyAPLoAeoRt049c9tp5o3gegWjoywCjuaYBmpvCQGTJxxMghMpXLml0gvjMCb_Ra1agV7AMhdLrEdXG8yDnJH7f5C_JOH2SAH8RqgnYVTQigjLAzXCZ9zGX700Y92RwEp_w0_TTXRPfHW5yJTQNHyZXwVqjAK7OTB1AYneZNsP7gzud127hp6GydoS0xQXtbOZjALJCFNmwjRoUCTYALCdD8aKXTtUi7sJpqpsTMobM1b2HFq_WGn7GJhZTHohyzZCGLKIxbJ5t6HpAUtMpa0Eajo6bRahanl9FYM7D94sEXdCriclvlOraHHOFzQX8t.NO3pMir6w4Uv2qcMt-oA4gITip_PFgdkZdC5kgeIe-o"
Response Headers - HttpStatus 200
access-control-allow-origin: *
cache-control: no-cache
content-type: application/json; charset=utf-8
date: Mon, 30 Jul 2018 22:41:34 GMT
expires: Thu, 01 Jan 1970 00:00:00 GMT
pragma: no-cache
server: Kestrel
transfer-encoding: chunked
ClaimIdentity
SecurityToken
Getting Roles
Request
curl -X GET "https://localhost:5001/api/Auth/GetRoles" -H "accept: application/json" -H "Authorization: Bearer eyJhbGciOiJBMjU2S1ciLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIiwidHlwIjoiSldUIn0.V9QoFJE3HGRLIGNAMnqUwLDhoIwRkM4oBJWOdR7swnx-D80ttcWkxWLZvJlLo2mrjOOqz2w6fxr7kpOGVpxOa9sak0XirDYe.JA0T9RjdxsEdbGAhM-FoDA.JxdItRfs7OSfl96dT-rd9kuoH0FW2lnPc2Aa3uXvtJNqW0XgrG6GwoRv_biIAglhfa-1FWHwPbSnEmD8H0TmdX_Z6d1oWyRivbgy8Bjyh1DWVIRm7hq4C0sRCDWNHjQlGt0dVipkcNM48gPGpBG3DiBCheyzA9-W5gp_mipqTgzZ1qAh1HYdHu0K4A4lZqCjjlQr0UJ2FBVzcj4FkbaH0id4t9aK1vYwak8QU3i3ZS6ZOZ9HIeWTXgUcV4j6lEDyYyAPLoAeoRt049c9tp5o3gegWjoywCjuaYBmpvCQGTJxxMghMpXLml0gvjMCb_Ra1agV7AMhdLrEdXG8yDnJH7f5C_JOH2SAH8RqgnYVTQigjLAzXCZ9zGX700Y92RwEp_w0_TTXRPfHW5yJTQNHyZXwVqjAK7OTB1AYneZNsP7gzud127hp6GydoS0xQXtbOZjALJCFNmwjRoUCTYALCdD8aKXTtUi7sJpqpsTMobM1b2HFq_WGn7GJhZTHohyzZCGLKIxbJ5t6HpAUtMpa0Eajo6bRahanl9FYM7D94sEXdCriclvlOraHHOFzQX8t.NO3pMir6w4Uv2qcMt-oA4gITip_PFgdkZdC5kgeIe-o"
Response Headers - HttpStatus 500
content-type: text/html; charset=utf-8
date: Mon, 30 Jul 2018 22:54:32 GMT
server: Kestrel
transfer-encoding: chunked
Retriving Current User
Claims on Current Context
Is Admin
Request
curl -X GET "https://localhost:5001/api/Auth/IsAdmin" -H "accept: application/json" -H "Authorization: Bearer eyJhbGciOiJBMjU2S1ciLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIiwidHlwIjoiSldUIn0.V9QoFJE3HGRLIGNAMnqUwLDhoIwRkM4oBJWOdR7swnx-D80ttcWkxWLZvJlLo2mrjOOqz2w6fxr7kpOGVpxOa9sak0XirDYe.JA0T9RjdxsEdbGAhM-FoDA.JxdItRfs7OSfl96dT-rd9kuoH0FW2lnPc2Aa3uXvtJNqW0XgrG6GwoRv_biIAglhfa-1FWHwPbSnEmD8H0TmdX_Z6d1oWyRivbgy8Bjyh1DWVIRm7hq4C0sRCDWNHjQlGt0dVipkcNM48gPGpBG3DiBCheyzA9-W5gp_mipqTgzZ1qAh1HYdHu0K4A4lZqCjjlQr0UJ2FBVzcj4FkbaH0id4t9aK1vYwak8QU3i3ZS6ZOZ9HIeWTXgUcV4j6lEDyYyAPLoAeoRt049c9tp5o3gegWjoywCjuaYBmpvCQGTJxxMghMpXLml0gvjMCb_Ra1agV7AMhdLrEdXG8yDnJH7f5C_JOH2SAH8RqgnYVTQigjLAzXCZ9zGX700Y92RwEp_w0_TTXRPfHW5yJTQNHyZXwVqjAK7OTB1AYneZNsP7gzud127hp6GydoS0xQXtbOZjALJCFNmwjRoUCTYALCdD8aKXTtUi7sJpqpsTMobM1b2HFq_WGn7GJhZTHohyzZCGLKIxbJ5t6HpAUtMpa0Eajo6bRahanl9FYM7D94sEXdCriclvlOraHHOFzQX8t.NO3pMir6w4Uv2qcMt-oA4gITip_PFgdkZdC5kgeIe-o"
Response Headers - HttpStatus 403
content-length: 0
date: Mon, 30 Jul 2018 22:58:49 GMT
server: Kestrel
That behivior didn’t happen when i create a SecurityToken withouth an EncryptingCredentials.
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
...
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
var authBuilder = services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
});
authBuilder.AddJwtBearer(cfg =>
{
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
var binarySigningKey = Convert.FromBase64String(AppSettings.Keys.SigningKey);
// var binaryDecryptionKey = Convert.FromBase64String(AppSettings.Keys.DecryptionKey);
cfg.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = AppSettings.Keys.JwtIssuer,
ValidAudience = AppSettings.Keys.JwtAudience,
IssuerSigningKey = new SymmetricSecurityKey(binarySigningKey),
// TokenDecryptionKey = new SymmetricSecurityKey(binaryDecryptionKey),
ClockSkew = TimeSpan.Zero
};
});
...
}
AuthController.cs
[ApiController]
[Route("api/[controller]/[action]")]
public class AuthController : ControllerBase
{
...
// GET api/values/5
[HttpPost]
public async Task<ActionResult> Logon([FromBody]UsuarioModel model)
{
var user = await _userManager.FindByEmailAsync(model.Logon);
if (user == null)
return NotFound();
var result = await _signInManager.PasswordSignInAsync(user, model.Password, true, false);
if (!result.Succeeded)
return BadRequest();
var binSigning = Convert.FromBase64String(AppSettings.Keys.SigningKey);
// var binEncrypting = Convert.FromBase64String(AppSettings.Keys.DecryptionKey);
var claims = new ClaimsIdentity();
claims.AddClaims(new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString())
});
var roles = await _userManager.GetRolesAsync(user);
claims.AddClaims(roles.Select(role => new Claim(ClaimTypes.Role, role)));
var keySigning = new SymmetricSecurityKey(binSigning);
var signing = new SigningCredentials(keySigning, SecurityAlgorithms.HmacSha256);
// var keyEncrypting = new SymmetricSecurityKey(binEncrypting);
// var encrypting = new EncryptingCredentials(keyEncrypting, SecurityAlgorithms.Aes256KW, SecurityAlgorithms.Aes256CbcHmacSha512);
var expires = DateTime.Now.AddDays(30);
var handler = new JwtSecurityTokenHandler();
var token = new JwtSecurityToken(
issuer: AppSettings.Keys.JwtIssuer,
audience: AppSettings.Keys.JwtAudience,
claims: claims.Claims,
notBefore: DateTime.Now,
expires: expires,
signingCredentials: signing);
// var token = handler.CreateToken(new SecurityTokenDescriptor
// {
// Issuer = AppSettings.Keys.JwtIssuer,
// Audience = AppSettings.Keys.JwtAudience,
// Subject = claims,
// NotBefore = DateTime.Now,
// Expires = expires,
// IssuedAt = DateTime.Now,
// SigningCredentials = signing,
// EncryptingCredentials = encrypting
// });
return Ok(handler.WriteToken(token));
}
...
}
SecurityToken
Token Validated
Current User
Copied from original issue: aspnet/Security#1829
Issue Analytics
- State:
- Created 5 years ago
- Reactions:2
- Comments:8 (4 by maintainers)
Top GitHub Comments
This just wasted an hour of my life. Why is there a global static singleton configuration again, that ASP.NET Core sets implicitly, and is then inherited by anything created by me, unless I explicitly clear it?
@TobyMosque I am going to close this, if this doesn’t work for you, please reopen.