Keypair Lifecycle
OIDC mandates RS256, so the JwtService must be constructed with an RSA key pair rather than a shared HMAC secret. The private key signs access tokens and ID tokens; the matching public key is published at the JWKS endpoint so relying parties can verify them.
Generating a key pair
With openssl
# 2048-bit RSA private key (PKCS#8 PEM)
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out oidc_private.pem
# Optional: derive the public key (the server derives this for you from the private key)
openssl rsa -in oidc_private.pem -pubout -out oidc_public.pemWith Node's generateKeyPairSync
import { generateKeyPairSync } from "node:crypto";
const { privateKey } = generateKeyPairSync("rsa", { modulusLength: 2048 });
const pem = privateKey.export({ format: "pem", type: "pkcs8" }).toString();
// Persist `pem` to your secret store — do not regenerate on boot (see below).PEM vs KeyObject
JwtService accepts either form for the key option:
import { JwtService } from "@jmondi/oauth2-server";
// PEM string (e.g. loaded from an env var or secret manager)
new JwtService({ key: process.env.RSA_PRIVATE_KEY_PEM });
// Node KeyObject (e.g. from generateKeyPairSync or createPrivateKey)
import { createPrivateKey } from "node:crypto";
new JwtService({ key: createPrivateKey(process.env.RSA_PRIVATE_KEY_PEM) });A PEM is convenient to store and transport; a KeyObject avoids re-parsing the PEM on every operation. Both produce the same kid — an RFC 7638 thumbprint — so the JWKS kid stays stable across restarts as long as the key bytes are stable.
Do not generate the key on boot
Generating a fresh key pair at startup invalidates every previously issued token and rotates the kid on each restart, breaking relying parties mid-session. Generate the key once, store it in a secret manager (or an env var injected at deploy time), and load the same key on every boot. The keys in this documentation are generated inline only because the examples are throwaway.
Storing the key
- Keep the private key in a secret manager (AWS Secrets Manager, GCP Secret Manager, Vault) or an injected environment variable — never commit it.
- Only the public key is exposed, and only through the JWKS endpoint.
- The JWKS response is cacheable (
Cache-Control: public, max-age=3600); relying parties refetch it when they see an unknownkid.
Multi-key rotation
v1 is a single-key model: one active RSA key, one entry in the JWKS. Rotating the key today means a hard cutover — issue with the new key, and tokens signed by the old key stop verifying once the old key is no longer published.
True overlapping rotation (publishing multiple keys in the JWKS so old and new tokens both verify during a grace window) and ES256 support are a forward-looking change tracked for a future major version. Design your deployment so the key can be swapped via configuration, and prefer short access-token lifetimes to shrink the cutover window.