Token Signature Validation Failing Reported that Token Does Not Have a Kid

1.3k views Asked by At

In setting up a web service to take in a token generated from a B2C call to https://login.microsoftonline.com/{tenant Id}/oauth2/v2.0/token I keep getting the IDX10503 error.

I generate the token in Postman which uses the api's client Id and client secret and all is good. But when I send it in to the api's jwt token handling, I get the error detailed below. Note I reuse the client secret as the key in the example. Is that my issue? If so where do I actually get the secret in B2C?

How can I resolve this?


Error

SecurityTokenSignatureKeyNotFoundException: IDX10503: Signature validation failed. Token does not have a kid. Keys tried: '[PII of type 'System.Text.StringBuilder' is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'. Number of keys in TokenValidationParameters: '1'. Number of keys in Configuration: '0'.

Minimally Reproducable Code
var secret = "{B2C Registered App Client Secret}";

var jwt = "eyJ0eXA...Z5zA";
var jwtSource = jwt.Replace("Bearer ", "");
var secretBytes = Encoding.ASCII.GetBytes(secret);

// Doesn't show the PII text, unclear why, but out of the scope for this error.
IdentityModelEventSource.LogCompleteSecurityArtifact = true;

// Validate
var TokenHandler = new JwtSecurityTokenHandler();
var validationParameters = new TokenValidationParameters
{
    ValidateIssuerSigningKey = true,
    ValidateAudience = false,
    ValidateIssuer = false,
    IssuerSigningKey = new Microsoft.IdentityModel.Tokens.SymmetricSecurityKey(secretBytes)
};

/* Exception thrown here */
var result = TokenHandler.ValidateToken(jwtSource, 
                                        validationParameters, 
                                        out Microsoft.IdentityModel.Tokens.SecurityToken validatedToken);

Postman Token Generation

Postman Access Url (998c is the proper TenantId): https://login.microsoftonline.com/998c...dbe3/oauth2/v2.0/token

Token Result - Jwt.IO Report f
{
  "typ": "JWT",
  "nonce": "MWR2cRS...CBAI_Bc",
  "alg": "RS256",
  "x5t": "9GmnyF...Lo7Y",
  "kid": "9GmnyF...Lo7Y"
}

Discovery Document

https://{TenantName}.b2clogin.com/{TenantName}.onmicrosoft.com/b2c_1_signupsignin/discovery/v2.0/keys

"keys": [
        {
            "kid": "X5e...aNk",
            "nbf": 1493763266,
            "use": "sig",
            "kty": "RSA",
            "e": "AQAB",
            "n": "tVKUt...QVXw"
        }

Reply To Dave D

The metadataUrl (login.microsoft...) reports an html page back as "You need to login". I believe the actual one is reported in B2C.

So for the B2C instance's discovery doc the actual open-id url is https://{tenant}.b2clogin.com/{tenant}.onmicrosoft.com/b2c_1_signupsignin/v2.0/.well-known/openid-configuration"; and I changed it as such.

Updated Dave D Code

I had to update the issuers to not get an error when processing code about unknown issuer

var jwt = "eyJ...{Fresh Postman Token w/client id/secret}...5A";

var tenantId = "998...be3"; // tenantId in Dave D example 
var tenant = "{b2C Tenant Name}";

IdentityModelEventSource.ShowPII = true;
IdentityModelEventSource.LogCompleteSecurityArtifact = true;

//var metadataUrl = $"https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token";
var metadataUrl = $"https://{tenant}.b2clogin.com/{tenant}.onmicrosoft.com/b2c_1_signupsignin/v2.0/.well-known/openid-configuration";

var oidcConfigurationManager 
= new ConfigurationManager<OpenIdConnectConfiguration>
    (metadataUrl, 
     new OpenIdConnectConfigurationRetriever(), 
     new HttpDocumentRetriever());
     
var metadataDocument = await oidcConfigurationManager.GetConfigurationAsync(CancellationToken.None);

var tokenHandler = new JwtSecurityTokenHandler();

var result = await tokenHandler.ValidateTokenAsync(jwt, new TokenValidationParameters
{
    ValidIssuer = metadataDocument.Issuer,
    ValidIssuers = new[] { $"https://{tenant}.b2clogin.com/{tenantId}/v2.0/",
                           $"https://sts.windows.net/{tenantId}/"},
    ValidateIssuer = true,
    IssuerSigningKeys = metadataDocument.SigningKeys,
    ValidateIssuerSigningKey = true,
    ValidateLifetime = true,
    ValidateAudience = false,
});
Exception Garnered upon call to Validate

IDX10503: Signature validation failed. Token does not have a kid.

Keys tried: 'Microsoft.IdentityModel.Tokens.RsaSecurityKey, 
KeyId: 'X5e...{key1}...BwaNk', InternalId: 'X5e...{key1}...BwaNk'. ,
KeyId: X5eXk4xyojNFum1kl2Ytv8dlNP4-c57dO6QGTVBwaNk

'. Number of keys in TokenValidationParameters: '1'.
Number of keys in Configuration: '0'.
Exceptions caught: ''.
token: 'eyJ0...W_AOPHAqo5A'. 
See https://aka.ms/IDX10503 for details.
1

There are 1 answers

4
Dave D On

The value used to sign the token isn't your app secret defined in the app registration, that secret is just to secure the calls your app makes to B2C/Entra ID.

You get the keys to verify signing from the jwks_uri endpoint defined in the OpenID Connect metadata document.

In your case I can see you're using the token endpoint https://login.microsoftonline.com/{tenant Id}/oauth2/v2.0/token, which is defined in the metadata document at https://login.microsoftonline.com/{tenant Id}/v2.0/.well-known/openid-configuration, so it's that document's jwks_uri you'll need to use to get the signing keys.

Given that you're using .NET there are libraries to help with loading & caching the OpenID Connect metadata doc, so you should be able to use the following code:

var metadataUrl = $"https://login.microsoftonline.com/{tenantId}/v2.0/.well-known/openid-configuration";

var oidcConfigurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(metadataUrl, new OpenIdConnectConfigurationRetriever(), new HttpDocumentRetriever());
var metadataDocument = await oidcConfigurationManager.GetConfigurationAsync(CancellationToken.None);

var tokenHandler = new JwtSecurityTokenHandler();

var result = await tokenHandler.ValidateTokenAsync(jwt, new TokenValidationParameters
{
    ValidIssuer = metadataDocument.Issuer,
    ValidateIssuer = true,
    IssuerSigningKeys = metadataDocument.SigningKeys,
    ValidateIssuerSigningKey = true,
    ValidateLifetime = true,
    ValidateAudience = false,
});

That example uses System.IdentityModel.Tokens.Jwt 7.0.0 and Microsoft.IdentityModel.Protocols.OpenIdConnect 7.0.0

PS: I'm assuming you need the Entra ID v2 metadata document based on the token endpoint you posted. If you're passing a policy to the token endpoint (making it a B2C token request, rather than an Entra ID token request) then you need to use the B2C metadata which would be https://{tenantName}.b2clogin.com/{tenantName}.onmicrosoft.com/{policyName}/v2.0/.well-known/openid-configuration.

PPS: if you want to show the info hidden by the PII warning then the line you need is IdentityModelEventSource.ShowPII = true.