JWT Configuration
TL;DR — SCHEMABOUND rotates JWT signing keys without downtime by accepting a comma-separated list via
SCHEMABOUND_JWT_SECRETS. New tokens are signed with the last entry; old entries remain valid until explicitly removed. Supports HS256, RS256, and ES256 algorithms configured per-issuer viaJWT_ALGORITHM.Use this page if you need to: configure JWT secret rotation for zero-downtime key changes, set up OIDC token validation against external identity providers (Entra ID, Okta, Keycloak), or troubleshoot expired/invalid JWT errors.
Configuration Variables
| Variable | Default | Purpose |
|---|---|---|
SCHEMABOUND_JWT_SECRETS | (required) | Comma-separated list of signing keys; last entry is current, earlier entries validate until removed |
JWT_ALGORITHM | HS256 | Signing algorithm per issuer: HS256, RS256, or ES256 |
Rolling Key Rotation
SCHEMABOUND supports zero-downtime JWT secret rotation by accepting multiple secrets in a single configuration. The runtime evaluates keys in order from newest to oldest during validation; the last entry is used for new token signing while earlier entries remain active for validating tokens issued before rotation.
# Before rotation (single key)
SCHEMABOUND_JWT_SECRETS=old_secret_key_here JWT_ALGORITHM=HS256
# During rotation (two keys — old validates, new signs)
SCHEMABOUND_JWT_SECRETS=new_key_abc123,old_secret_key_here JWT_ALGORITHM=HS256
# After rotation complete (single key again)
SCHEMABOUND_JWT_SECRETS=new_key_abc123 JWT_ALGORITHM=HS256
Rotation Procedure
- Generate a new signing secret using your preferred secure generator (e.g.,
openssl rand -hex 32) - Add the new key to the front of
SCHEMABOUND_JWT_SECRETSwhile keeping the old key at the end - Restart SCHEMABOUND (or trigger a hot-reload via admin endpoint) — both keys are now active for validation; new tokens sign with the front entry
- Wait until all previously-issued tokens have expired (check
expclaim) - Remove the old key from the end of
SCHEMABOUND_JWT_SECRETSand restart
External Identity Provider Verification
When SCHEMABOUND validates JWTs issued by external IdPs (Entra ID, Okta, Keycloak), it fetches public keys from the provider’s JWKS endpoint rather than using symmetric secrets:
# OIDC issuer URL for JWKS discovery
SCHEMABOUND_OIDC_ISSUER=https://login.microsoftonline.com/<tenant-id>/v2.0
# Or explicit JWKS endpoint URL (if not discoverable via OIDC)
SCHEMABOUND_JWKS_ENDPOINT=https://login.microsoftonline.com/common/discovery/v2.0/keys
Public keys are cached in memory and refreshed on a configurable interval (SCHEMABOUND_JWKS_CACHE_TTL, default 1 hour). Expired cache entries trigger an async refresh without blocking request processing.
Algorithm Selection
| Algorithm | Use Case | Key Type | Example Provider |
|---|---|---|---|
HS256 | Internal service-to-service auth; fast signing/validation | Symmetric shared secret | Self-signed, development mode |
RS256 | External IdP verification with public/private key pairs | Asymmetric RSA (2048+ bit) | Okta, Keycloak, Azure AD |
ES256 | External IdP verification; smaller signatures than RSA | Asymmetric EC P-256 | Entra ID, Google Identity Platform |
Troubleshooting
“Invalid signature” errors after key rotation
If clients report invalid_signature after adding a new key to SCHEMABOUND_JWT_SECRETS, verify:
- The old key is still listed at the end of the comma-separated list
- No leading/trailing whitespace in individual keys
- The restart completed before new tokens were issued (check SCHEMABOUND logs for “JWT secrets loaded” message)
Expired token errors from IdP-issued JWTs
External IdP tokens have fixed expiration windows (typically 1 hour). If SCHEMABOUND receives token_expired responses but the IdP says the token is valid:
- Check system clock synchronization — NTP drift causes timestamp validation failures
- Verify
SCHEMABOUND_OIDC_ISSUERmatches the IdP’s actual issuer URL (trailing slashes matter)