---
title: "Keys, JWKS, and rotation"
description: "Select a key driver, publish public keys, and rotate without invalidating live tokens."
image: "https://adonisjs-jwt.pages.dev/og.png"
---

> Documentation Index
> Fetch the complete documentation index at: https://adonisjs-jwt.pages.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Keys, JWKS, and rotation

## Environment keys

Use `node ace jwt:make-key` for a single issuer whose secret manager deploys PEM values to every
instance. Replace literal newlines with `\n` only when your environment format requires it; the
generated config restores them before importing the keys.

Changing an environment private key immediately invalidates tokens signed by the previous key. For
zero-downtime rotation, use the database driver.

## Encrypted database keys

```ts
keys: keyDrivers.database({ table: 'jwt_signing_keys' })
```

Private JWK material is encrypted through AdonisJS `Encryption`; public material and key IDs remain
available for verification and JWKS. All application instances read the same active and overlapping
keys.

### Rotation runbook

1. Confirm every instance has the same `APP_KEY`/encryption key ring and can read the signing-key table.
2. Confirm the configured access-token TTL and clock tolerance. This defines the minimum overlap.
3. Run the command once from a release or scheduled job:

```sh
   node ace jwt:rotate-keys
```

4. The driver introduces and promotes a new Ed25519 key, retaining the previous public key as
   `verifying`.
5. Confirm newly issued tokens use the new `kid` and both keys appear in JWKS.
6. Do not force retirement. A later rotation retires verifying keys only after more than one strict
   access TTL has elapsed since promotion.
7. Alert on command failure and `jwt:key_rotated`; never run concurrent rotation jobs.

Refresh tokens are opaque and unaffected by signing-key rotation.

## Public JWKS

When enabled, `GET /.well-known/jwks.json` returns verification keys with
`Cache-Control: public, max-age=<cacheMaxAge>`. Private JWK parameters (`d`, `p`, `q`, `dp`, `dq`,
`qi`, `oth`, and symmetric `k`) are never returned.

Choose `cacheMaxAge` shorter than the planned introduce-to-promote window so downstream verifiers
can fetch the new public key before it signs traffic.

## Verify an external issuer

```ts
keys: keyDrivers.remoteJwks({
  url: 'https://id.example.com/.well-known/jwks.json',
  cacheMaxAge: 600_000,
  cooldownDuration: 30_000,
  timeoutDuration: 5_000,
})
```

Remote JWKS is verification-only and honors `alg`, `kid`, `use`, and key operations during key
selection. Pin `issuer`, `audience`, and the asymmetric algorithm to the external provider's exact
contract. Use HTTPS and do not accept a user-controlled JWKS URL.

## HS256

HS256 is available for controlled symmetric deployments:

```ts
keys: keyDrivers.env({ secret: env.get('JWT_SECRET'), kid: 'hmac-2026-08' })
```

The secret must contain at least 32 bytes. Every verifier that receives it can also mint tokens, so
prefer Ed25519 when verification crosses service boundaries. HS256 secrets are never exposed via
JWKS.

Source: https://adonisjs-jwt.pages.dev/key-management/index.mdx
