Learning should always be fun

How to create and verify JWT tokens via JWK endpoints for your microservices in node js

How to create and verify JWT tokens via JWK endpoints for your microservices in node js

Hello gud ppl! In this tutorial we will see how can we create our own public JWK endpoint to verify our JWT token. JWT has been widely used in the design of modern microservices. It facilitates us to transfer payload via securely signed signature. One of the widely adopted method of signing JWT is RSA which uses private and public keys to sign and verify token respectively. These keys can be represented in either a pem file or a string or also JWK.

 

Lets get started…

JWK stands for JSON Web Key. A typical JWK for private key would look like :

{
  "keys": [
    {
      "kty": "RSA",
      "kid": "pos6sKqyyi1LmTP1-hjHpPR7-1nrqUhgk6CPhry-YvY",
      "n": "2pDe9zBdvpmtPrqg-7RMldTKT6FMdw1YhleyhsoqY0dUEVk08kgkilVisvsKr5cExDSHDCwyz9edj9DF2Bm7YQ",
      "e": "AQAB",
      "d": "FkZDYt-7_gu9WzI768sBLxfjkl_24f8rMW3IlPIPhdRz0L9YoMhfMkT_7ClWkZwwWraPJMf2MNCN6UX8SE7WTQ",
      "p": "-zYkOgqXYcR_n4j2X543oyRSgleiu0aqcoKQ52SOaAM",
      "q": "3rttConSkWFd8DHTzxlkhNB8y_cSfpjTESXcWGc8a8s",
      "dp": "aMFmD_IUuIdZdOyHWM5Agz6FTac_y_qm30OFK4jqPYM",
      "dq": "JevXNtIcTbA8JCb3nuz91jcA6GEafv9aADNn_o0lFl0",
      "qi": "oEQGatYl7VazeAPRxK6h53iHecYTYQjYVkY4TRK0MiE"
    },
    {
      "kty": "RSA",
      "kid": "ZHuDkZMFZHh7rd0sA1jUnmoqMxElow8i5n4S7UX7jwM",
      "n": "z08t46yQghDYY9dln4ZSHdBTDrX3M-uK2yvHDEfsDfzR5xES-qNJXZ3vLb2Vsom9GzY-4dS1abG6dplhaAkK1Q",
      "e": "AQAB",
      "d": "w6ZLfdLPsyDoyBlx_EMNXrvMl1aeje6fZseDHJEINA2R6LgqHwIAANw5P-HR6dQDFQdhDmGWFnW0giOItAjJoQ",
      "p": "9X8yzDW33gs7qY3fprMI7Bh22fHHxkdQg-ww8oDZWek",
      "q": "2C27W0XjwxblxFKEdC7_j4YM-qZTCcfl-70jpN99ag0",
      "dp": "kU32PRRWfeBcMeE9TSeO0l8wiZMn0V4Ic-zqk75b53E",
      "dq": "zlNnnIeqCMtT5Pq0_IbW188jmB8i5hTqRkiROo0sEAk",
      "qi": "U3gO2J5P4QaaxpgjU7738wkQIHbNn6pFsOJu0MvSPKQ"
    }
  ]
}

In order to understand this we must know a little about Public-Key Cryptography Standards (PKCS). The public key consists of two parameters “n” and “e” where:

n: the RSA modulus, a positive integer
e: the RSA public exponent, a positive integer

[ref:https://tools.ietf.org/html/rfc3447#section-3.1]

 

Similarly the private key consists of

n: the RSA modulus, a positive integer
d: the RSA private exponent, a positive integer
p: the first factor, a positive integer
q: the second factor, a positive integer
dP: the first factor's CRT exponent, a positive integer
dQ: the second factor's CRT exponent, a positive integer
qInv: the (first) CRT coefficient, a positive integer
r_i: the i-th factor, a positive integer
d_i: the i-th factor's CRT exponent, a positive integer
t_i: the i-th factor's CRT coefficient, a positive integer

[ref:https://tools.ietf.org/html/rfc3447#section-3.2]

You may dig into each of these you self but important thing to understand here is that the integer here are represented using the base64url encoding. So the public key for the above JWK of private key will be as :

{
  "keys": [
    {
      "kty": "RSA",
      "kid": "pos6sKqyyi1LmTP1-hjHpPR7-1nrqUhgk6CPhry-YvY",
      "n": "2pDe9zBdvpmtPrqg-7RMldTKT6FMdw1YhleyhsoqY0dUEVk08kgkilVisvsKr5cExDSHDCwyz9edj9DF2Bm7YQ",
      "e": "AQAB"
    },
    {
      "kty": "RSA",
      "kid": "ZHuDkZMFZHh7rd0sA1jUnmoqMxElow8i5n4S7UX7jwM",
      "n": "z08t46yQghDYY9dln4ZSHdBTDrX3M-uK2yvHDEfsDfzR5xES-qNJXZ3vLb2Vsom9GzY-4dS1abG6dplhaAkK1Q",
      "e": "AQAB"
    }
  ]
}

Now if you look closer we will see that there is array of keys in the keys object. These keys have a kid which uniquely identifies each keys. We will crate a token just in a moment but lets see a JWT created signed from the above private key:

eyJhbGciOiJSUzI1NiIsImtpZCI6InBvczZzS3F5eWkxTG1UUDEtaGpIcFBSNy0xbnJxVWhnazZDUGhyeS1ZdlkifQ.eyJrZXkiOiJobHcifQ.fnu05rRspMJiD2KBpsD9g2SGUD3gbuqN1qC_lfIz4OJvWQ2J2fMe--QxN09oAomkiWjRzxU3JHOvO45gbfl2Lw

This is just a base64encode text so if you decode it in some popular JWT decoding sites you will see the header field as :

{
  "alg": "RS256",
  "kid": "pos6sKqyyi1LmTP1-hjHpPR7-1nrqUhgk6CPhry-YvY"
}

We can see that the header field of the JWT has the kid of the key that signed it. So any JWT verifying library will verify this token by searching the kid from the keys of public JWK.

 

Before we begin

Before we begin lets create a two private and public key via openssl.
Create private key:

openssl genrsa -out k1.private 1024
openssl genrsa -out k2.private 1024

You may want to create public key as well but that is not necessary as the key has both public and private parts.

For this tutorial we will use node-jose library. Lets create a file as jwtHelper.js

const jose = require('node-jose');
const fs = require('fs');
const path = require('path');
const keystore = jose.JWK.createKeyStore();
const opts = {
    algorithms: ["RS256"],
};

const privkeys = [
    fs.readFileSync(path.join("k1.private")),
    fs.readFileSync(path.join("k2.private")),
];

async function creteKeystore() {
    await keystore.add(privkeys[1], 'pem');
    await keystore.add(privkeys[0], 'pem');
}

async function createPublicJWK() {
    const jwk = keystore.toJSON();
    return jwk;
}

async function createPrivateJWK() {
    // giving true as argument will give private JWK
    const jwk = keystore.toJSON(true);
    return jwk;
}

async function createJWT(payload) {
    let token = await jose.JWS
        .createSign({ format: 'compact' }, keystore.all()[0])
        .update(JSON.stringify(payload))
        .final();
    return token;
}

async function verifyJWT(token, jwk) {
    const keystore = await jose.JWK.createKeyStore();
    for (const key of jwk.keys) {
        await keystore.add(key);
    }
    const result = await jose.JWS
        .createVerify(keystore, opts)
        .verify(token);
    return result;
}

exports.creteKeystore = creteKeystore;
exports.createPublicJWK = createPublicJWK;
exports.createPrivateJWK = createPrivateJWK;
exports.createJWT = createJWT;
exports.verifyJWT = verifyJWT;

 

Creating JWT and Verifying via JWK

We will use our jwtHelper module to create JWT and we will also verify the token. Lets create a index.js file:

const jwtHeler = require("./jwtHelper");

async function init() {
    const payload = {
        msg: "Hello!"
    };
    await jwtHeler.creteKeystore();
    const publicJWK = await jwtHeler.createPublicJWK();
    const privateJWK = await jwtHeler.createPrivateJWK();
    const token = await jwtHeler.createJWT(payload);
    const result = await jwtHeler.verifyJWT(token, publicJWK);

    console.log("public jwk :: ", JSON.stringify(publicJWK));
    console.log("\n");
    console.log("private jwk :: ", JSON.stringify(privateJWK));
    console.log("\n");
    console.log("token :: ", token);
    console.log("\n");
    console.log("payload :: ", result.payload.toString());

}
init();

Here we first created the public JWK. After that we created a JWT token from the private JWK. This token is then finally verified via the public JWK.

 

Conclusion

I hope you like the tutorial. In addition to this you can also add a http endpoint and provide public JWT for other parties. From these endpoint they can verify the provided tokens. But doing so DON’T MAKE PRIVATE JWK visible to any one.

The source code can be found here.

 

Reference:

RSA: https://en.wikipedia.org/wiki/RSA_(cryptosystem)

JWK: https://tools.ietf.org/html/rfc7517

PKCS: https://tools.ietf.org/html/rfc3447