I'm using Auth0 and parsing its idToken server-side like this:
var tokenHandler = new JwtSecurityTokenHandler();
var jwtToken = tokenHandler.ReadJwtToken(idToken); // idToken comes from client using auth0.js
var sub = jwtToken.Claims.First(claim => claim.Type == "sub").Value;
The above code works well and I'm able to parse the idToken successfully, but I'd like to validate the idToken before trusting it, so I've tried this:
string clientSecret = "{client_secret}"; // comes from Auth0 application's client secret
var validations = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "some value", // used "iss" from here: https://jwt.io/
ValidAudience = "some value", // used "aud" from here: https://jwt.io/
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(clientSecret)),
};
var principal = tokenHandler.ValidateToken(idToken, validations, out var validatedToken);
When trying to validate the token, it results in this exception:
Microsoft.IdentityModel.Tokens.SecurityTokenSignatureKeyNotFoundException
HResult=0x80131500
Message=IDX10501: Signature validation failed. Unable to match key:
kid: '[PII is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'
I grabbed the issuer and audience values by parsing one of the tokens here: https://jwt.io/. The client secret is my application's client secret at Auth0.
How can I validate Auth0's idToken using JwtSecurityTokenHandler?
To manually validate Auth0's JWT token, you need these 2 Nuget packages:
System.IdentityModel.Tokens.Jwt
Microsoft.IdentityModel.Protocols.OpenIdConnect
Then get these values from Auth0's application settings
string auth0Domain = ""; // Note: if your Domain is foo.auth0.com, this needs to be https://foo.auth0.com/
string auth0ClientId = "";
Validate Auth0's token as follows:
IConfigurationManager<OpenIdConnectConfiguration> configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>($"{auth0Domain}.well-known/openid-configuration", new OpenIdConnectConfigurationRetriever());
OpenIdConnectConfiguration openIdConfig = await configurationManager.GetConfigurationAsync(CancellationToken.None);
var validations = new TokenValidationParameters
{
ValidIssuer = auth0Domain,
ValidAudiences = new[] { auth0ClientId },
IssuerSigningKeys = openIdConfig.SigningKeys
};
var user = tokenHandler.ValidateToken(idToken, validations, out var validatedToken);
With the validated token, you can extract useful info like this:
var securityToken = (JwtSecurityToken)validatedToken;
var userId = user.Claims.First(c => c.Type == ClaimTypes.NameIdentifier).Value;
Source: Manually validating a JWT using .NET
Credit: #Nkosi
Related
I implemented a Github project and don't understand how the user and the key system for Jwt work.
I now have a secret-key which is located in the AppSettings and when the user logs in, the following function is executed:
private string GenerateJwtToken(string username)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_appSettings.token);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[] { new Claim("username", username) }), //<-
Expires = DateTime.UtcNow.AddMinutes(30),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
So if I get it right then I generate here the token for the logged in user but what does the line with the username mean?
After that I store the username and the token inside the sessionStorage and if I trigger some other Controller where the [Authorize] attribute is defined, I add followed Header with the fetch:
headers: {
'Content-type': 'application/json',
'Authorization': `Bearer ${sessionStorage.getItem("token")}`,
},
Then it first run into this functions:
public async Task Invoke(HttpContext context, IAuthService authService)
{
var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
if (token != null)
attachUserToContext(context, authService, token);
await _next(context);
}
private void attachUserToContext(HttpContext context, IAuthService authService, string token)
{
try
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
tokenHandler.ValidateToken(token, new TokenValidationParameters //<- Error
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false,
ClockSkew = TimeSpan.Zero
}, out SecurityToken validatedToken);
var jwtToken = (JwtSecurityToken)validatedToken;
var userId = int.Parse(jwtToken.Claims.First(x => x.Type == "username").Value);
context.Items["User"] = "user";
}
catch
{
// do nothing if jwt validation fails
// user is not attached to context so request won't have access to secure routes
}
}
So here it validates that the token from the Header is not null and then it tries to do what!? Also why is again the username used here?
When it run the ValidateToken function it return a error: IDX12709: CanReadToken() returned false. JWT is not well formed: '[PII of type 'System.String' is hidden
https://github.com/cornflourblue/dotnet-5-jwt-authentication-api/tree/279c8058669bbfa59902a4473f62e5371167340c
JWT is split into three parts.
The first one is a header with information like encrypting algorithm.
The second part is the payload, where you actually can find your claims, in your case, it's gonna be a username, exp date,
you can add here whatever you need.
Unique username or id is added here for the server to know to who this token belongs and who is calling the server.
In other case how would you figure out who is calling the server, you would have to save this token in some database or somewhere with the assigned user.
The last part is the signature, this part is verified with your secret key to knowing that it's not some fake token, but actually, a token created by you.
Parts are split by dots.
Hard to say why your token validation fails, at first I would try checking your created token on https://jwt.io and see what's inside.
Maybe you validate token with bearer prefix, as it's written that token is formed badly, so it can be the issue.
One more thing instead of using custom middleware auth, you can use the JWT default authentication scheme
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = false,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII
.GetBytes(Configuration.GetSection("AppSettings:Token").Value)),
//...
};
});
I would like to validate a JWS token with "alg": "ES256", "x5c" header fields only.
x5c field contain 3 certificates. I'm able to load them but .net return a IDX10503 error telling me kid field is missing.
here is my code :
var handler = new System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler();
var jwtSecurityToken = handler.ReadJwtToken(token);
var x5cList = JsonConvert.DeserializeObject<List<string>>(jwtSecurityToken.Header.X5c);
var certs = ValidateCertificate(x5cList.ToList());
var keys = certs.Select(cert => new X509SecurityKey(cert)).ToList();
SecurityToken validatedToken;
handler.ValidateToken(jwtSecurityToken.RawData, new TokenValidationParameters
{
ValidateLifetime = true,
ValidateIssuer = true,
ValidateIssuerSigningKey = true,
IssuerSigningKeys = keys,
TryAllIssuerSigningKeys = true,
}, out validatedToken);
Anyone know why it's not working ? I also tried to don't load keys by myself thinking that JwtSecurityTokenHandler may be able to do it by it self but it's not working as well.
When I load this token on jwt.io it says it is validated...
I am using a WebApp in Azure with Azure Active Directory authentication turned on and and am pulling the id_token from the /.auth/me page here:
From my understanding when I paste this into JWT.io it validates the signature by looking up the OpenId Configuration at https://login.microsoftonline.com/{tenantId}/v2.0/.well-known/openid-configuration
and then uses the jwks_uri (https://login.microsoftonline.com/{tenantId}/discovery/v2.0/keys) to find the public signing key (x5c Array) based on the kid from the JWT header here:
However when I try to replicate this scenario in C# I am unable to validate the signature using the System.IdentityModel.Tokens.Jwt library.
Here are a few of my attempts and results:
ATTEMPT
var idToken = "{id_token}";
var signingKey = "{x5c[0]}";
SecurityToken validatedToken;
ClaimsPrincipal claimsPrincipal;
var handler = new JwtSecurityTokenHandler();
var symmetricSecurityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("-----BEGIN CERTIFICATE-----\n" + signingKey + "\n-----END CERTIFICATE-----"));
var tokenValidationParameters = new TokenValidationParameters()
{
IssuerSigningKey = symmetricSecurityKey,
ValidateIssuerSigningKey = true,
ValidateLifetime = false,
ValidateAudience = false,
ValidateIssuer = false
};
try
{
claimsPrincipal = handler.ValidateToken(idToken, tokenValidationParameters, out validatedToken);
Console.WriteLine($"{claimsPrincipal.Identity.Name}");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
RESULT
IDX10501: Signature validation failed. Unable to match key: kid:
'System.String'. Exceptions caught: 'System.Text.StringBuilder'.
token: 'System.IdentityModel.Tokens.Jwt.JwtSecurityToken'.
ATTEMPT
var kid = "l3sQ-50cCH4xBVZLHTGwnSR7680";
var idToken = "{id_token}";
var signingKey = "{x5c[0]}";
SecurityToken validatedToken;
ClaimsPrincipal claimsPrincipal;
var handler = new JwtSecurityTokenHandler();
var symmetricSecurityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("-----BEGIN CERTIFICATE-----\n" + signingKey + "\n-----END CERTIFICATE-----")) { KeyId = kid, };
var tokenValidationParameters = new TokenValidationParameters()
{
IssuerSigningKey = symmetricSecurityKey,
ValidateIssuerSigningKey = true,
ValidateLifetime = false,
ValidateAudience = false,
ValidateIssuer = false
};
try
{
claimsPrincipal = handler.ValidateToken(idToken, tokenValidationParameters, out validatedToken);
Console.WriteLine($"{claimsPrincipal.Identity.Name}");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
RESULT
IDX10511: Signature validation failed. Keys tried:
'System.Text.StringBuilder'. kid: 'System.String'. Exceptions
caught: 'System.Text.StringBuilder'. token:
'System.IdentityModel.Tokens.Jwt.JwtSecurityToken'.
I also tried both attempts without the "-----BEGIN CERTIFICATE-----\n" and "\n-----END CERTIFICATE-----" additions to the signing key and had the same results as well as trying with ASCII encoding instead of UTF8.
==== UPDATE ====
I was able to use the Microsoft.IdentityModel.Protocols and Microsoft.IdentityModel.Protocols.OpenIdConnect libraries to validate the token using the following code to create the signing keys:
var issuer = jwtToken.Issuer;
var audiences = jwtToken.Audiences;
var issuerOpenIdUri = $"{issuer}/.well-known/openid-configuration";
string stsDiscoveryEndpoint = issuerOpenIdUri;
var configManager = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint, new OpenIdConnectConfigurationRetriever());
OpenIdConnectConfiguration config = configManager.GetConfigurationAsync().Result;
...and then use the OpenIdConnectConfiguration to assign the keys to the TokenValidatetionParameters:
var tokenValidationParameters = new TokenValidationParameters()
{
ValidAudiences = audiences,
ValidIssuer = issuer,
IssuerSigningKeys = config.SigningKeys,
ValidateIssuerSigningKey = true,
ValidateLifetime = true,
ValidateAudience = true,
ValidateIssuer = true
};
However I am still curious why my original attempts would not work. I would like to understand a little better what is happening under the hood.
Microsoft.IdentityModel.Tokens.X509SecurityKey uses the certificate thumbprint in base 16 in its KeyId. Azure Active Directory KeyId uses the certificate thumbprint in base 64.
To use same key ID format as that used by Azure Active directory ,It will be possible (Just the way you did) with Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectConfigurationRetriever and by using signing keys this way
myTokenValidationParameters.IssuerSigningKeys = myOpenIdConnectionConfiguration.SigningKeys;
The error may also occur when “aud” claim doesn’t match the audience which is nothing but the client Id and can be mitigated by giving proper scopes in AAD and in code with proper audience and scope and by granting consent.
I have IdentityServer4 that generates signed JWT tokens.
In my web api I added auth middleware to validate these tokens:
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
Authority = env.IsProduction() ? "https://www.example.com/api/" : "http://localhost/api/",
AllowedScopes = { "WebAPI", "firm",
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile },
RequireHttpsMetadata = env.IsProduction(),
});
It works perfectly. However, I suspect it doesn't verify signature of jwt token because there is no public key configured to validate token. How to configure token signature validation?
PS: I try to use UseJwtBearerAuthentication instead this way:
var cert = new X509Certificate2("X509.pfx", "mypassword");
var TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
ValidateIssuer = true,
ValidIssuer = env.IsProduction() ? "https://www.example.com/api/" : "http://localhost/api/",
IssuerSigningKey = new X509SecurityKey(cert),
};
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
Authority = env.IsProduction() ? "https://www.wigwam3d.com/api/" : "http://localhost/api/",
Audience = "WebAPI",
RequireHttpsMetadata = env.IsProduction(),
TokenValidationParameters = TokenValidationParameters
});
It also works (and I hope validates token signature also!) but gives me another bug:
UserManager.GetUserAsync(HttpContext.HttpContext.User)
return null, while using UseIdentityServerAuthentication returns me correct User
I think there is no need to add certificate to you API for validation. .UseIdentityServerAuthentication() middleware calls your IdentiyServer to retrieve public key on startup from https://www.example.com/api/.well-known/openid-configuration. At least that's my understanding how it works.
Finally I done it with JwtBearerAuthentication,
GetUserAsync function failure can be fixed with call to:
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
because of this issue: https://github.com/aspnet/Security/issues/1043
Any ideas to configure the same using IdentityServer auth are welcome!
I have a scenario where there are many separate clients connecting via JWT token.
The client (browser) first needs to login (and is given a JWT token)
The client then needs to retrieve their account information, they do this by sending a request to the server (which includes the JWT token. The server (which has access to the secret) reads the JWT token (securely) and should send back the user information, how do I do this?
p.s. Each client has a different secret
I can do this on a per app basis
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AllowedAudiences = new[] { audience },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, secret)
},
Provider = new CookieOAuthBearerProvider("authCookie")
});
But this method will not work on a per request basis....
This is a snippet from what we're currently using (connecting to AzureAD).
You'll need to implement GetSigningCertificates which returns IEnumerable<X509SecurityToken> to validate the JWT is properly signed.
internal static ClaimsPrincipal GetClaimPrincipalFromToken(string jwtSecurityHeader)
{
var jwtSecurityHandler = new JwtSecurityTokenHandler();
var signingCertificates = GetSigningCertificates(ConfigHelper.FederationMetadataDocument);
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidAudience = ConfigHelper.AppIdURI,
ValidIssuer = ConfigHelper.Issuer,
LifetimeValidator =
(before, expires, token, parameters) =>
{
//Don't allow not-yet-active tokens
if (before.HasValue && before.Value > DateTime.Now)
return false;
//If expiration has a date, add 2 days to it
if (expires.HasValue)
return expires.Value.AddDays(2) > DateTime.Now;
//Otherwise the token is valid
return true;
},
ValidateLifetime = true,
IssuerSigningTokens = signingCertificates,
};
var headerParts = jwtSecurityHeader.Split(' ');
if (headerParts.Length != 2 || headerParts[0] != "Bearer")
throw new AuthorizationException(HttpStatusCode.Forbidden, "Invalid token type");
var jwtSecurityToken = headerParts[1];
SecurityToken jwtToken;
var claimsPrincipal = jwtSecurityHandler.ValidateToken(jwtSecurityToken, tokenValidationParameters, out jwtToken);
return claimsPrincipal;
}
You'll need to tweak it a bit for your application, but this should get you most of the way there. Note that this code is parsing a {HeaderType} {Token} format (for example Bearer {token}). If you're simplying parsing the {Token}, you need to remove the .Split(' ')