Background
If you are unfamiliar with JWTs, please check out A Primer on JSON Web Tokens.
Generating RSA keys using openssh
See RSA key formatting for details on how to create a key pair, and how to transform them into the correct format for node-jose.
These keys can now be used by node-jose to verify the signature.
Signing with the private key
The first thing we need to do is create a keystore and add the private key to it. This is so we can determine the key id (kid):
async function getKid() {
const fs = require("fs");
const pubPem = fs.readFileSync("request_key.pub").toString();
const jose = require("node-jose");
const keystore = jose.JWK.createKeyStore();
await keystore.add(pubPem, "pem");
console.log(keystore.toJSON().keys[0].kid);
}
getKid().then();
This program will output something like:
xKIeQfluQFxYPsAmWKvTH-HwzCYA7lKf6HgXRie8zRU
Using this we can now create a signed JWT:
async function sign() {
const fs = require("fs");
const jose = require("node-jose");
// read private key
const privPem = fs.readFileSync("request_key.pkcs8").toString();
// create key store
const keystore = jose.JWK.createKeyStore();
// add the private key to the key store
await keystore.add(privPem, "pem");
// get the key based on the kid from previous step
const privKey = keystore.get("xKIeQfluQFxYPsAmWKvTH-HwzCYA7lKf6HgXRie8zRU");
// create the JWT
let payload = { d: "e" };
r = await jose.JWS.createSign(
{ format: "compact", fields: { alg: "RS256" } },
privKey,
)
.update(JSON.stringify(payload))
.final();
// print the JWT as a string to the console
console.log(r);
}
sign().then();
Run this program and observe the JWT being printed:
eyJhbGciOiJSUzI1NiIsImtpZCI6InhLSWVRZmx1UUZ4WVBzQW1XS3ZUSC1Id3pDWUE3bEtmNkhnWFJpZTh6UlUifQ.eyJkIjoiZSJ9.bMLSVkGYeFtLz77-YWKMsPVgoKVOWDkEhrifXGFJt3v5cpC_9_1EsPyBV1kA6Ez411m7vm7s7mGi_DxTO_VMAUvhzUouxNxzSThYZKJz1ESTvBYo9Cvfxl3lGk_tLlT5_6rptET4GC1aUhzBkpwz55VgRiqtJelf9pQe4a8wMAl3pbTVnfpuXvPBCmTxdy6zddC2fs8kmdV_e58Fw5ZXnduRgai5S-JtxQFfH6QF4QjudF1BwQu046DidSz6SZtXgAlltisG6Pw8Vb9yvwLxGe8aSUOXyi83q1nC8Vb1uaqP9v8-GQQcHRm_RbtMA7klXdn-APfS3Du54In59XWzHxs1NOCWxHhY89Y6xaN8i8J0_mI_Dy8CmaBg3TfXXOaBO2K-qxKKQG7JZ0wXAMskiJsL5f121Z3vbFs-hhIqnP-V2DCUbvopqMf11aSSWGMH0NtjIkyldoU2fan3hJNsCR1DQ8BJb9A3Y4zHHPgh-I7Lo7nuCn0__3lt9X_6cbQK
This token is then saved in the token.txt file. Note, when running the signing program you could redirect the output to token.txt e.g.
node sign.js > token.txt
Verifying with the public key
The public key can be used to verify this token was signed by the private key:
async function verify() {
const fs = require("fs");
const jose = require("node-jose");
// read public key
const pubPem = fs.readFileSync("request_key.pub").toString();
// create key store
const keystore = jose.JWK.createKeyStore();
// add the private key to the key store
await keystore.add(pubPem, "pem");
// read the saved token
const token = fs.readFileSync("token.txt").toString();
// verify the token
let r = await jose.JWS.createVerify(keystore).verify(token);
// print the token payload
console.log(r.payload.toString());
}
verify().then();
The following is the output when we run this:
{"d":"e"}
If a different key is used for encoding we can see the createVerify call raises an error:
node_modules/node-jose/lib/jws/verify.js:131
reject(new Error("no key found"));
...
This shows the key that was used for encoding is not in the keystore used for verifying.
Summary
This is a brief intro to RSA signing JWTs in node-jose, which will hopefully shortcut any future work where we are dealing with JWTs in Node.js.