1

I am trying to use jsonwebtoken NPM package for verifying a JWT token issued by Azure Active Directory. Following is the node.js code that I have written:

  var jwt = require('jsonwebtoken');
  var token = '<valid JWT token>';
  var x5cString = '<x5cSTring>';
  var publicKey = '-----BEGIN CERTIFICATE-----\n' + x5cString + '\n-----END CERTIFICATE-----';

    var verifiedToken = jwt.verify(token, publicKey) //, verifyOptions);

Please note that in the above code I use the actual x5c String from https://login.microsoftonline.com/common/discovery/keys. This works fine and I get the expected result. But, the X5C string which is the public key keeps changing. I am trying to understand how to get this public key automatically.

EDIT

I found some sample code on Jsonwebtoken NPM package web site. In this code signingKey is what I want. Following is the code.

var jwksClient = require('jwks-rsa');
var client = jwksClient({
  jwksUri: 'https://login.microsoftonline.com/common/discovery/keys'
});
function getKey(header, callback){
  client.getSigningKey(header.kid, function(err, key) {
    var signingKey = key.publicKey || key.rsaPublicKey;
    callback(null, signingKey);
  });
}

jwt.verify(token, getKey, options, function(err, decoded) {
  console.log(decoded.foo) // bar
});

In the above code, jwt.verify calls getKey that takes header and callback as parameter. I do not understand how jwt.verify function passed 'header' parameter to the getKey. Following is the header that I have retrieved. how do I pass this header to getKey in the jwt.verify?

var decoded = jwt.decode(token, {complete: true});
var header = decoded.header
KurioZ7
  • 6,028
  • 13
  • 46
  • 65

3 Answers3

6

Okay so I have found out the solution. Following is the final code that I have.

var jwksClient = require('jwks-rsa');
var jwt = require('jsonwebtoken');

    token = 'valid JWT token';
    var decoded = jwt.decode(token, {complete: true});
    var header = decoded.header

    var verifyOptions = {
     algorithms: ['RS256'],
     header: decoded.header

  };


    var client = jwksClient({
      jwksUri: 'https://login.microsoftonline.com/common/discovery/keys'
    });
    function getKey(header, callback){
      client.getSigningKey(header.kid, function(err, key) {
        var signingKey = key.publicKey || key.rsaPublicKey;
        callback(null, signingKey);
      });
    }

    jwt.verify(token, getKey, verifyOptions, function(err, decoded) {
      //This will display the decoded JWT token.
      console.log(decoded)  
    });
KurioZ7
  • 6,028
  • 13
  • 46
  • 65
0

If you are OK with using jsonwebtoken indirectly, you could use

See https://github.com/justinlettau/azure-ad-verify-token/blob/master/src/verify.ts for how https://www.npmjs.com/package/azure-ad-verify-token does this, code that compares well with your own answer!

There's also the older https://www.npmjs.com/package/azure-jwt-verify (not maintained, and has some security vulnerabilities) which does something very very similar.

For the officially suggested libraries, try here https://learn.microsoft.com/en-us/azure/active-directory/develop/reference-v2-libraries#web-application

Simon B.
  • 2,530
  • 24
  • 30
0

thanks to KurioZ7 for his answer that lead me on the right path, along with https://learn.microsoft.com/en-us/answers/questions/785644/nodejs-backend-needs-to-verify-valid-user-signed-i that lead me to the following.

// get jwt node module to verify the token
import jwt from 'jsonwebtoken';

// get jwks client to make fetching the MS public key for your token
import jwksClient from 'jwks-rsa';

// decode your token
const decoded = jwt.decode(token, { complete: true });

// set your token options
const verifyOptions = { algorithms: ['RS256'], header: decoded?.header };

// use a new client to fetch pubkey from MS
const client = jwksClient({ jwksUri: 'https://login.microsoftonline.com/common/discovery/keys' });

// fetch public key based on your token kid (you can do this manually as per the page link above by hitting the ms link
const key = await client.getSigningKey(decoded?.header.kid).then((key) => key.publicKey || key.rsaPublicKey);

// use that pubkey to verify the token
const result = jwt.verify(token, key, verifyOptions);

This should return the payload from the token or throw one of many errrors. Wrap in a try catch or promise to play with handling of the error states

Hope this helps explain whats going on

Paul Smith
  • 151
  • 1
  • 6