1
votes

I'm following this article to implement Azure B2C in our Node.JS app. I got back JWT token and trying to validate signature. Using jsonwebtoken npm module to verify my token. Also, I got public keys from the OpenID Connect metadata endpoint. They are in JSON and looks like this:

{   "keys": [{
        "kid": "some kid value",
        "nbf": some number,
        "use": "sig",
        "kty": "RSA",
        "e": "AQAB",
        "n": "some long key"
    }, {
        "kid": "some kid value",
        "nbf": some number,
        "use": "sig",
        "kty": "RSA",
        "e": "AQAB",
        "n": "some long key"
    }, {
        "kid": "some kid value",
        "nbf": some number,
        "use": "sig",
        "kty": "RSA",
        "e": "AQAB",
        "n": "some long key"
    }]
}

So when I'm trying to pass 'n' value from appropriate key to the

jwt.verify(token, 'my n value go here',  { algorithms: ['RS256'] }, callbackFunction());

I got

Error: PEM_read_bio_PUBKEY failed

I feel like I'm passing the wrong key, and I could not find any explanation about how I can use this public key metadata to validate tokens. The only helpful line from the article:

A description of how to perform signature validation is outside the scope of this document. Many open source libraries are available to help you with this if you need it.

How do I validate a signature?

1
Did you get the keys from here: login.microsoftonline.com/common/discovery/keys? Because there is also a property called x5c which looks more like a key.juunas
@juunas, no, I'm getting keys from this link as identified in this tutorial, so there is no x5c field in keys...Roman Gusiev
This article explains the metadata endpoints and the keys you need to use to do token validation.Daniel Dobalian

1 Answers

7
votes

So, I found the answer in the source code of Passport-Azure-AD

  1. Go to 'lib' folder in source code and find aadutils.js file.
  2. Line number 142. There is a function rsaPublicKeyPem(key1, key2)

    exports.rsaPublicKeyPem = (modulusB64, exponentB64) => {
       const modulus = new Buffer(modulusB64, 'base64');
       const exponent = new Buffer(exponentB64, 'base64');
    
       const modulusHex = prepadSigned(modulus.toString('hex'));
       const exponentHex = prepadSigned(exponent.toString('hex'));
    
       const modlen = modulusHex.length / 2;
       const explen = exponentHex.length / 2;
    
       const encodedModlen = encodeLengthHex(modlen);
       const encodedExplen = encodeLengthHex(explen);
       const encodedPubkey = `30${encodeLengthHex(
          modlen +
          explen +
          encodedModlen.length / 2 +
          encodedExplen.length / 2 + 2
          )}02${encodedModlen}${modulusHex}02${encodedExplen}${exponentHex}`;
    
       const derB64 = new Buffer(encodedPubkey,'hex').toString('base64');
    
       const pem = `-----BEGIN RSA PUBLIC KEY-----\n${derB64.match(/.{1,64}/g).join('\n')}\n-----END RSA PUBLIC KEY-----\n`;
    
      return pem;
    };
    
  3. I copied whole aadutils library and called this function with keys

    const aadutils = require('./aadutils');
    const jwt = require('jsonwebtoken');
    
    //key is an object from public endpoint. Just follow the tutorial
    const pubKey = aadutils.rsaPublicKeyPem(key.n, key.e);
    jwt.verify(id_token, pubKey, { algorithms: ['RS256'] }, function(err, decoded) {
        //do what you want next
    });
    

Got my signature validated.