Generate JSON Web Tokens

This guide generates an RS256 key pair and signs a short-lived JSON Web Token (JWT). Run this code in a trusted backend environment so the private key never reaches a browser or client app.

Conduit also supports ES256 with P-256 keys and EdDSA with Ed25519 keys. The examples in this guide use RS256.

The examples use an empty payload and add these fields:

  • alg identifies the RS256 signing algorithm.
  • kid identifies the public key that Conduit uses to verify the signature.
  • iat records when the signer created the token.
  • exp limits the token lifetime to 10 minutes.

A JWT is signed, not encrypted. Anyone with the token can decode its header and payload, so don’t include secrets in either part.

Generate a key pair

The run-once scripts generate a 2,048-bit RSA private key in PKCS #8 format and a public key in SubjectPublicKeyInfo (SPKI) format. Both files use Privacy-Enhanced Mail (PEM) encoding. They stop instead of replacing an existing key pair.

Save the script as generate-keys.mjs or generate-keys.sh.

1import { generateKeyPairSync } from 'node:crypto'
2import { existsSync, writeFileSync } from 'node:fs'
3
4const privateKeyPath = 'private_key.pem'
5const publicKeyPath = 'public_key.pem'
6
7if (existsSync(privateKeyPath) || existsSync(publicKeyPath)) {
8 throw new Error('Key files already exist; refusing to replace them')
9}
10
11const { privateKey, publicKey } = generateKeyPairSync('rsa', {
12 modulusLength: 2048,
13 privateKeyEncoding: {
14 type: 'pkcs8',
15 format: 'pem',
16 },
17 publicKeyEncoding: {
18 type: 'spki',
19 format: 'pem',
20 },
21})
22
23writeFileSync(privateKeyPath, privateKey, { mode: 0o600 })
24writeFileSync(publicKeyPath, publicKey, { mode: 0o644 })
25
26console.log(`Created ${privateKeyPath} and ${publicKeyPath}`)

Run the example:

$node generate-keys.mjs

Keep private_key.pem secret. In the Conduit app, register public_key.pem as an RS256 public key, bind it to a Nodes API key, and copy the generated Key ID. For the complete dashboard flow, read the JWT authentication overview.

Sign a token

Always sign JWTs on your server. Never expose the private key or signing code in a browser or client app. For client-side RPC requests or transaction submission, your server can mint a short-lived JWT and return it to the client, which can cache it until shortly before it expires and send it with the API key endpoint URL. Alternatively, your server can sign the JWT and make the RPC request.

Set KEY_ID to the Key ID shown in Conduit. Each example reads private_key.pem, adds the required protected header and time claims, then prints the signed token.

Save the example as generate-jwt.ts or generate-jwt.go.

1import { readFile } from 'node:fs/promises'
2import { importPKCS8, SignJWT } from 'jose'
3
4const algorithm = 'RS256'
5const keyId = process.env.KEY_ID
6
7if (!keyId) {
8 throw new Error('Set KEY_ID to the Key ID from Conduit')
9}
10
11const privateKeyPEM = await readFile('private_key.pem', 'utf8')
12const privateKey = await importPKCS8(privateKeyPEM, algorithm)
13
14const token = await new SignJWT({})
15 .setProtectedHeader({ alg: algorithm, kid: keyId })
16 .setIssuedAt()
17 .setExpirationTime('10m')
18 .sign(privateKey)
19
20console.log(token)

Install the signing library and generate a token:

$npm install jose
$export KEY_ID='[YOUR_KEY_ID]'
$export JWT="$(npx tsx generate-jwt.ts)"

Generate tokens on demand, and use the shortest lifetime that works for your service. When a token approaches expiration, create a replacement before sending another request.

Next, make an authenticated RPC request with the token.