> 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.