Generating signed tokens in Node

I’d like to sign my own tokens rather than call the /token endpoint constantly (which I’ve got working, but I just want to avoid an extra API call in our app)

I’ve followed the instructions and checked out the example worker in the docs.

In my case I’m using Next.js and I’d like to use a Next API route rather than a Worker.

I’ve repurposed the example Worker code; it has a couple of deprecated functions (atob() and btoa()) which I updated (with Buffer.from(jwkKey, 'base64') and buffer.toString('base64') respectively). However the problem I have is that crypto is not available at all - it imports as undefined, so crypto.subtle.importKey() fails.

I’m not sure if this is because I’m running on localhost, and crypto requires a secure context, but localhost is supposed to be considered a secure context, and I am using the crypto module elsewhere in my code without any trouble (but not crypto.subtle)

Is there any updated advice on this? I’m using Node 14.17.1

Hey @tbtbtb! I would certainly recommend that you use one of the JWT libraries for node here. All Stream signed tokens are JWTs.

jsonwebtoken and jose are the ones recommended over at jwt.io.

The example code in the docs use browser APIs (Cloudflare Workers mirrors the browser APIs available for crypto) which are different than the ones available in node.

OK, thanks @renan

This turned out to be a little bit more involved than I assumed, but here’s what I did - might help the next person.

I used jsonwebtoken. The key bit of code is:
const signedToken = jwt.sign(data, jwkKey, { header: {"kid": keyID}, algorithm: "RS256"})

You can then use signedToken in the same way as the Cloudflare Worker example uses the variable of the same name.

However, the parameter jwkKey is NOT what Cloudflare gives you, not exactly.

What Cloudflare provides is a base64 encoded jwk. This contains the various values (modulus, exponent, and so on - see rfc7518 section 6.3 that are needed to create the public and private keys.

However, jsonwebtoken’s sign() method needs the private key, not the jwk. I was able to get the private key by using jwk-to-pem. This is easy to do as a one-off by using this tool: https://npm.runkit.com/jwk-to-pem

First decode your Cloudflare-provided jwk value:

const jwk = JSON.parse(Buffer.from(jwkKey, ‘base64’))
console.debug(“jwk is:”, jwk)

Next you use jwk-to-pem like so - if you are pasting into the runkit link above, it will look like this:

var jwkToPem = require('jwk-to-pem');
var options = { private: true };
var jwk = {
  use: 'sig',
  kty: 'RSA',
  kid: '<removed>',
  alg: 'RS256',
  n: '<removed>',
  e: '<removed>',
  d: '<removed>',
  p: '<removed>',
  q: '<removed>',
  dp: '<removed>',
  dq: '<removed>',
  qi: '<removed>'
}
pem = jwkToPem(jwk, options);
console.log(pem);

After short time that will provide your private key in the familiar format like:

-----BEGIN PRIVATE KEY-----
<removed>
-----END PRIVATE KEY-----

You can save that as a string / environment variable (being careful to keep the newlines) or in a private key file, and use it in the jsonwebtoken sign() method, second parameter.

Finally you use the resulting jwt as instructed in the Cloudflare docs and it works.

1 Like

This topic was automatically closed 3 days after the last reply. New replies are no longer allowed.