I am trying to build a method which validates my tokens. I am retrieving my tokens from Azure Active Directory with Open Id Connect Authorization Code Flow. The tokens that I get are the access_token and the id_token. I am using .NET Core.
My validation code is as follows:
string stsDiscoveryEndpoint = "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration";
var handler = new JwtSecurityTokenHandler();
ConfigurationManager<OpenIdConnectConfiguration> configManager = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint, new OpenIdConnectConfigurationRetriever());
OpenIdConnectConfiguration config = configManager.GetConfigurationAsync().Result;
try
{
TokenValidationParameters validationParameters = new TokenValidationParameters
{
ValidIssuers = new [] { "https://login.microsoftonline.com/tenantid/v2.0" },
ValidAudiences = new [] { "client-Id" },
ValidateAudience = true,
ValidateIssuer = true,
IssuerSigningKeys = config.SigningKeys,
ValidateLifetime = true
};
var tokenHandler = new JwtSecurityTokenHandler();
SecurityToken validatedToken = null;
tokenHandler.ValidateToken(token.AccessToken, validationParameters, out validatedToken);
return validatedToken != null;
}
catch (SecurityTokenInvalidSignatureException ex)
{
return false;
}
catch(SecurityTokenValidationException)
{
return false;
}
The code below works for the id_token BUT does not work for the access_token
The error message which I am getting when this method is executed for the access_token is:
IDX10511: Signature validation failed. Keys tried: 'Microsoft.IdentityModel.Tokens.X509SecurityKey , KeyId: CtAAALb-8NsDe333734859crfOc '. kid: 'CtAAALb-8NsDe333734859crfOc'. Exceptions caught: ' '
the nonce header has to be SHA2 hashed before signature verification
Here is an example of code where you can see
jsonToken.Header.Add("nonce", hashedNonce);
private static bool _hashNonceBeforeValidateToken = true;
private const string MicrosoftGraphApplicationId = "00000003-0000-0000-c000-000000000000";
private const string MicrosoftIssuer = "https://sts.windows.net";
public static bool ValidateTokenSignature(string accessToken, ApplicationConfiguration applicationConfiguration) {
var tokenHandler = new JwtSecurityTokenHandler();
var jsonToken = tokenHandler.ReadJwtToken(accessToken);
string[] parts = accessToken.Split('.');
string header = parts[0];
string payload = parts[1];
string signature = parts[2];
//hash nonce and update header with the hash before validating
if (_hashNonceBeforeValidateToken &&
jsonToken.Header.TryGetValue("nonce", out object nonceAsObject))
{
string plainNonce = nonceAsObject.ToString();
using (SHA256 sha256 = SHA256.Create())
{
byte[] hashedNonceAsBytes = sha256.ComputeHash(
System.Text.Encoding.UTF8.GetBytes(plainNonce));
string hashedNonce = Base64Url.Encode(hashedNonceAsBytes);
jsonToken.Header.Remove("nonce");
jsonToken.Header.Add("nonce", hashedNonce);
header = tokenHandler.WriteToken(jsonToken).Split('.')[0];
accessToken = $"{header}.{payload}.{signature}";
}
}
//get the Microsoft JWT signature public key
string stsDiscoveryEndpoint = $"https://login.microsoftonline.com/{applicationConfiguration.TenantId}/v2.0/.well-known/openid-configuration";
if (jsonToken.Header.TryGetValue("ver", out object version) && version.ToString() == "1.0")
{
stsDiscoveryEndpoint = $"https://login.microsoftonline.com/{applicationConfiguration.TenantId}/.well-known/openid-configuration";
}
var openidConfigManaged = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint,
new OpenIdConnectConfigurationRetriever(),
new HttpDocumentRetriever());
var configTask = openidConfigManaged.GetConfigurationAsync();
configTask.Wait();
var config = configTask.Result;
var parameteres = new TokenValidationParameters()
{
RequireAudience = true,
ValidateAudience = true,
ValidAudiences = new[] { applicationConfiguration.ApplicationId, MicrosoftGraphApplicationId },
ValidateIssuer = true,
ValidIssuers = new string[] { $"{MicrosoftIssuer}/{applicationConfiguration.TenantId}/", config.Issuer },
IssuerSigningKeys = config.SigningKeys,
ValidateIssuerSigningKey = true,
RequireExpirationTime = true,
ValidateLifetime = true,
};
var claimPrincipal = tokenHandler.ValidateToken(
accessToken, parameteres, out SecurityToken validatedToken);
return claimPrincipal.Identity.IsAuthenticated;
}
Is the access_token
audience your API or Microsoft Graph/other 3rd party service? It only makes sense to validate the tokens that you (your service) consumes, other audiences will take care of this on their own. On top of that, the signature of that JWT may be opaque to you.
See this for more - https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/812#issuecomment-456700813
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With