Skip to main content

JWT Key Management

Allegro signs member session tokens with per-tenant RSA-2048 key pairs using RS256. This page covers the key lifecycle and how to manage keys in production.

Key Lifecycle

active → retiring → expired
StatusDescription
activeThe current signing key. All new tokens are signed with this key. Appears in JWKS.
retiringA previously active key after rotation. Still appears in JWKS until retires_at. Allegro accepts tokens signed by this key indefinitely (until the token's own exp).
expiredNo longer appears in JWKS. Allegro still accepts tokens signed by this key until the token's own exp (1-year TTL).

Retiring keys stay in JWKS for 1 year (matching the JWT TTL) so that external consumers like AWS API Gateway can always verify any valid token.

Commands

Generate Initial Keys

Run once per tenant after provisioning (automatically called by tenant:create):

php artisan jwt:keys:generate --tenant=*

Idempotent — skips tenants that already have an active key. Omit --tenant=* to run for the current tenant context.

Rotate Keys

Generates a new active key and moves the current active key to retiring:

php artisan jwt:keys:rotate --tenant=*

All new tokens are signed with the new key immediately. Old tokens remain verifiable via JWKS for 1 year.

Prune Expired Keys

Marks retiring keys as expired once retires_at has passed. Runs daily via the scheduler — you should not need to run this manually:

php artisan jwt:keys:prune --tenant=*

Expired keys are not deleted — Allegro continues to accept tokens signed by them until the token's own exp. Only JWKS visibility is removed.

Scheduler

jwt:keys:prune is registered in the Laravel scheduler and runs daily automatically. No additional configuration is required.

Emergency Key Revocation

There is no automated revocation. If a private key is compromised:

  1. Run php artisan jwt:keys:rotate --tenant=<id> to immediately stop signing new tokens with the compromised key. Existing tokens signed by the compromised key remain verifiable via JWKS until you complete step 3.
  2. The compromised key enters retiring status and continues to appear in JWKS for up to 1 year (its retires_at). External consumers will still accept tokens signed by it during this window.
  3. If immediate revocation is needed, manually update the compromised key's status to expired in the tenant database. This removes it from JWKS immediately. However, external consumers (e.g., AWS API Gateway) cache JWKS responses — typically for up to 1 hour — so tokens signed by the compromised key may continue to be accepted until those caches expire. Clear JWKS caches on your external consumers where possible.