question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

JwtSecurityTokenHandler.CreateJwtSecurityToken renaming Claim Type Names.

See original GitHub issue

From @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

claimidentity

SecurityToken

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

getroles

Claims on Current Context

tokenvalidated

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

securitytoken

Token Validated

tokenvalidated

Current User

getroles

Copied from original issue: aspnet/Security#1829

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Reactions:2
  • Comments:8 (4 by maintainers)

github_iconTop GitHub Comments

2reactions
lostmsucommented, Jul 29, 2020

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?

1reaction
brentschmaltzcommented, Aug 10, 2018

@TobyMosque I am going to close this, if this doesn’t work for you, please reopen.

Read more comments on GitHub >

github_iconTop Results From Across the Web

String Claim Types Error when JWT Decoding?
NET will rename some of the claims behind the scene. To stop this, you need to turn of the claims mapping using: JwtSecurityTokenHandler....
Read more >
JwtSecurityTokenHandler Class (System.IdentityModel. ...
The JSON claim 'name' value is set to Type after translating using this mapping. The default value is ClaimTypeMapping.OutboundClaimTypeMap.
Read more >
ASP.NET Core and JSON Web Tokens - where are my claims?
In this post we went through the default behaviour in which JWT claims are being mapped to different names in .NET applications. By...
Read more >
Jwt claim typ. Sorted by: 7. Improve this question. This name i
Problem is that incoming tokens have sub claim of non-string type, but parser expects java. , "iss"). x5t: String 2. println ("Claim name:...
Read more >
Access Token doesn't contain a `sub` claim
Identity.Name is a simple shortcut to find the first claim whose type matches what's set for the identity's NameClaimType property.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found