> For a complete page index, fetch https://docs.conduit.xyz/llms.txt # Generate JSON Web Tokens > Generate an RS256 key pair and sign short-lived JSON Web Tokens for Conduit RPC requests. 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. > **Note** > > 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`. **`Node.js`** ```javascript Node.js import { generateKeyPairSync } from 'node:crypto' import { existsSync, writeFileSync } from 'node:fs' const privateKeyPath = 'private_key.pem' const publicKeyPath = 'public_key.pem' if (existsSync(privateKeyPath) || existsSync(publicKeyPath)) { throw new Error('Key files already exist; refusing to replace them') } const { privateKey, publicKey } = generateKeyPairSync('rsa', { modulusLength: 2048, privateKeyEncoding: { type: 'pkcs8', format: 'pem', }, publicKeyEncoding: { type: 'spki', format: 'pem', }, }) writeFileSync(privateKeyPath, privateKey, { mode: 0o600 }) writeFileSync(publicKeyPath, publicKey, { mode: 0o644 }) console.log(`Created ${privateKeyPath} and ${publicKeyPath}`) ``` **`Bash`** ```bash Bash #!/usr/bin/env bash set -euo pipefail private_key_path='private_key.pem' public_key_path='public_key.pem' if [[ -e "$private_key_path" || -e "$public_key_path" ]]; then echo 'Key files already exist; refusing to replace them' >&2 exit 1 fi openssl genpkey \ -algorithm RSA \ -pkeyopt rsa_keygen_bits:2048 \ -out "$private_key_path" openssl pkey \ -in "$private_key_path" \ -pubout \ -out "$public_key_path" chmod 600 "$private_key_path" chmod 644 "$public_key_path" echo "Created $private_key_path and $public_key_path" ``` Run the example: **`Node.js`** ```bash Node.js node generate-keys.mjs ``` **`Bash`** ```bash Bash bash generate-keys.sh ``` Keep `private_key.pem` secret. In the [Conduit app](https://app.conduit.xyz/), 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](/rpc-nodes/guides/jwt-authentication). ## Sign a token > **Note** > > 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`. **`TypeScript`** ```typescript TypeScript import { readFile } from 'node:fs/promises' import { importPKCS8, SignJWT } from 'jose' const algorithm = 'RS256' const keyId = process.env.KEY_ID if (!keyId) { throw new Error('Set KEY_ID to the Key ID from Conduit') } const privateKeyPEM = await readFile('private_key.pem', 'utf8') const privateKey = await importPKCS8(privateKeyPEM, algorithm) const token = await new SignJWT({}) .setProtectedHeader({ alg: algorithm, kid: keyId }) .setIssuedAt() .setExpirationTime('10m') .sign(privateKey) console.log(token) ``` **`Go`** ```go Go package main import ( "fmt" "log" "os" "time" "github.com/golang-jwt/jwt/v5" ) func main() { keyID := os.Getenv("KEY_ID") if keyID == "" { log.Fatal("set KEY_ID to the Key ID from Conduit") } privateKeyPEM, err := os.ReadFile("private_key.pem") if err != nil { log.Fatal(err) } privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(privateKeyPEM) if err != nil { log.Fatal(err) } now := time.Now() token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims{ "iat": now.Unix(), "exp": now.Add(10 * time.Minute).Unix(), }) token.Header["kid"] = keyID signedToken, err := token.SignedString(privateKey) if err != nil { log.Fatal(err) } fmt.Println(signedToken) } ``` Install the signing library and generate a token: **`TypeScript`** ```bash TypeScript npm install jose export KEY_ID='[YOUR_KEY_ID]' export JWT="$(npx tsx generate-jwt.ts)" ``` **`Go`** ```bash Go go mod init conduit-jwt-example go get github.com/golang-jwt/jwt/v5 export KEY_ID='[YOUR_KEY_ID]' export JWT="$(go run generate-jwt.go)" ``` 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](/rpc-nodes/guides/jwt-authentication/make-requests) with the token. > Generate an RS256 key pair and sign short-lived JSON Web Tokens for Conduit RPC requests.