"IDX10511: Signature validation failed" for Azure AD SPA application
Asked Answered
E

2

10

I have an SPA application derived from the Identity Platform sample that originally calls a Graph API.

I've changed the endpoint to call a local API.

The SPA uses Azure AD for authentication.

The API sample is derived from the VS 2019 project template for API.

,NET 4.7.2 - no .NET Core.

I can authenticate OK and both ID and access tokens are present when I do a network trace.

However, on the API side I get an error:

"IDX10511: Signature validation failed. Keys tried: 'Microsoft.IdentityModel.Tokens.X509SecurityKey, KeyId: '1E50B4475DAC931359D564309A3385FFAB7FB431', InternalId: 'f61f7746-3cff-4557-8b2c-b47fad9cf1e3'. , KeyId: 1E50B4475DAC931359D564309A3385FFAB7FB431"

Decoding the access token shows:

"{
  "typ": "JWT",
  "nonce": "G0Q6_BuYJUfZaBnX-l1Ox1eoncxXRT4KMThFBcn1-VA",
  "alg": "RS256",
  "x5t": "HlC0R12skxNZ1WQwmjOF_6t_tDE",
  "kid": "HlC0R12skxNZ1WQwmjOF_6t_tDE"
}"

Googling this, it appears that the signature fails because of the nonce in the header and this requires "special processing".

All the validation is being done by OWIN.

Any idea what this is or how to fix this?

Erubescence answered 14/2, 2020 at 0:22 Comment(2)
Could you please tell me how you configure Azure AD for your web api and how you call the web api?Cutlery
Yeah.. It sounds like a configuration issue on one side.Drawl
E
13

I didn't think it was a configuration issue because I've never seen any configuration that specifies signature.

So I started looking through msal.js - it's open source.

"User.Read" (the scope defined in the sample I used) is hardcoded in a number of places so I removed this scope from the sample and created a dummy one called "abc".

I also reconfigured Azure AD for the scope change.

Lo and behold, everything worked.

Even more interesting, the header is different:

{
  "typ": "JWT",
  "alg": "RS256",
  "x5t": "HlC0R12skxNZ1WQwmjOF_6t_tDE",
  "kid": "HlC0R12skxNZ1WQwmjOF_6t_tDE"
}

Notice that there is no nonce.

So I suspect that because the original sample used Microsoft Graph, "User.Read" implies some special Graph processing that adds the nonce that screws up the signature.

For reference.

Erubescence answered 18/2, 2020 at 22:11 Comment(1)
Brilliant sleuthing! This is also the issue with trying to use a validate-jwt policy in API Management. They seem to have a dedicated validate-azure-ad-token policy but at the time of this writing using the latter policy throws some sort of an internal error with API management itself. So I left with using validate-jwt in my policy. However, the token it was validating indeed did use a default https://graph.microsoft.com/.default scope to acquire the access token. Once I changed that to a custom scope, validate-jwt had no trouble validating the signature.Zing
C
6

if you still need the User.Read scope you can get a successfull signature verification by adding a middleware that hash the nonce before the authentication

app.UseMiddleware<HashNonceJwtHeaderMiddleware>();

here the middleware :

using System.IdentityModel.Tokens.Jwt;
using System.Security.Cryptography;


namespace MyApplication.Api.Infrastructure
{

    public class HashNonceJwtHeaderMiddleware
    {
        private readonly RequestDelegate _next;
        private static bool _hashNonceBeforeValidateToken = true;

        public HashNonceJwtHeaderMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public async Task InvokeAsync(HttpContext context)
        {
            string accessToken = context.Request.Headers["Authorization"].FirstOrDefault();
            if (string.IsNullOrWhiteSpace(accessToken)) { return; }

            accessToken = accessToken.Substring("Bearer ".Length);
            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];

            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 = Encode(hashedNonceAsBytes);
                    jsonToken.Header.Remove("nonce");
                    jsonToken.Header.Add("nonce", hashedNonce);
                    header = tokenHandler.WriteToken(jsonToken).Split('.')[0];

                    accessToken = $"{header}.{payload}.{signature}";

                    context.Request.Headers["Authorization"] = $"Bearer {accessToken}";
                }
            }

            await _next(context);
        }

        private string Encode(byte[] input)
        {
            var output = Convert.ToBase64String(input);
            output = output.Split('=')[0]; // Remove any trailing '='s
            output = output.Replace('+', '-'); // 62nd char of encoding
            output = output.Replace('/', '_'); // 63rd char of encoding
            return output;
        }
    }
}

here the configuration of the Auth :

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddMicrosoftIdentityWebApi((options) =>
                {
                    options.Audience = "00000003-0000-0000-c000-000000000000";
                },
                (options) =>
                {
                    options.ClientId = "your app registration client id";
                    options.TenantId = "your subscription tenant id";
                    options.Instance = "https://login.microsoftonline.com/";
                    options.Domain = "the domain of the tenant";
                },
                JwtBearerDefaults.AuthenticationScheme);

Happy coding !

Cambridgeshire answered 16/5, 2022 at 8:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.