Azure AD B2C - Token validation does not work

Viewed 6219

I would like to use Azure AD B2C but have several difficulties using it. One problem I have is to validate the signature of the token. First I wanted to validate the token "manually" using jwt.io.

According to the Microsoft Docs, validating the signature should work like this:

Your app can use the kid claim in the JWT header to select the public key in the JSON document that is used to sign a particular token. It can then perform signature validation by using the correct public key and the indicated algorithm.

My understandig: Grab the kid value out of the header, lookup the key in the metadata under the location of jwks_uri, (assumption) use the value of "n" to verify the signature.

enter image description here

But Jwt.io, jsonwebtoken.io, and jose-jwt all say, that the siganture is invalid.

What am I missing?

3 Answers

There is now a way to validate tokens using two npm packages.

Resources

The information was made available at: Microsoft - Example-verifying-validation-tokens

Package Versions Used

How To

The example below was modified from example in Microsoft - Example-verifying-validation-tokens

Retrieving "Issuer" and "User Flow" Name Values From Azure

Screenshot of Azure portal for

Retrieving Application Id

enter image description here

Code Example

import jwt from 'jsonwebtoken';
import jkwsClient from 'jwks-rsa';

// Variables that need to be defined based on your Azure B2C Configuration.
const jwksUri = 'https://MY-B2C-TENANT.b2clogin.com/MY-B2C-TENANT.onmicrosoft.com/MY-USER-FLOW-NAME/discovery/v2.0/keys';

const client = jkwsClient({
  jwksUri
});

export function getKey(header, callback) {
  client.getSigningKey(header.kid, (err, key) => {
    var signingKey = key.getPublicKey();
    callback(null, signingKey);
  });
}

export function isTokenValid(token, applicationId, issuerUri) {
  return new Promise((resolve) => {
    const options = {
      audience: [applicationId],
      issuer: [issuerUri]
    };
    jwt.verify(token, getKey, options, (err, decoded) => {
      if (err) {
        // eslint-disable-next-line no-console
        console.error('Jwt Validation Failed', err);
        resolve(false);
      } else {
        // eslint-disable-next-line no-console        
        console.debug(decoded)
        resolve(true);
      }
    });
  });
}

Usage

const applicationId = 'APPLICATION ID OF B2C TENANT'
const issuerUri = 'In the User Flow Properties'

const valid = isTokenValid('some token value without "Bearer", applicationId, issuerUri);

Notes

  • The jwksUri contains the user "User Policy". If you have multiple "User Policies", you will need to figure out how to instantiate the jkwsClient with correct jwksUri based on the each "User Policy" flow.
  • While you can leverage jwt.io or jwt.ms, a token can be decoded in the debugger console. This will give you some key information such as the "iss" (Issuer) and the "kid":
'e3R5cDogIkpXVCIsIGFsZzogIlJTMjU2Iiwga2lkOiAiWU9VX1NORUFLWV9SQUJCSVQifQ==.e2V4cDogMTU5NjYwMTc4NiwgbmJmOiAxNTk2NTk4MTg2LCB2ZXI6ICIxLjAiLCBpc3M6ICJodHRwczovL3doaXRlLXJhYmJpdC5iMmNsb2dpbi5jb20vZjIzNDZhMzBhLTk1ODEtNGU0ZC04MWQwLWQyZjk4NTQ3MWJhOS92Mi4wLyIsIHN1YjogImYzMmNjNmJhLWE5MTctNGE1Ni1hYjhmLWIyNGZmMTg1ODUyOCIsICIuLi4iOiAibW9yZSB2YWx1ZXMuLi4ifQ==.UNODECODEME'.split('.').map((value, index) => { 
   // The signature can't be decoded
   if(index > 1) { return value; }
     return atob(value);
   });

You can use https://play.golang.org/p/7wWMBOp10R to get a public key from modulus(n) and exponent(e). Then copy the entire output in jwt.io and you'll see the signature is valid.

Related