Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cannot validate AAD access token - IDX10511: Signature validation failed

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: ' '

like image 314
anonuser1 Avatar asked Nov 07 '22 15:11

anonuser1


2 Answers

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;
    }

like image 174
Spirit_Life Avatar answered Nov 13 '22 02:11

Spirit_Life


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

like image 40
evilSnobu Avatar answered Nov 13 '22 01:11

evilSnobu