JWT Configuration
TL;DR — SCHEMABOUND rotates JWT signing keys without downtime via the
JwtConfigtrait hierarchy. New tokens are signed with the last configured key; old entries remain valid until explicitly removed. Supports HS256, RS256, and ES256 algorithms configured per-issuer through polymorphic config structs.
Configuration Trait Hierarchy
JWT configuration is pluggable via the JwtConfig trait. Each config type handles a specific signing scenario:
| Config Type | Use Case | Key Type |
|---|---|---|
SymmetricJwtConfig | Internal service-to-service auth, development mode | Shared secret (HS256) |
AsymmetricJwtConfig | External IdP verification with public/private key pairs | RSA/EC key pair (RS256, ES256) |
OidcJwksConfig | OIDC discovery-based validation against external providers | Fetches from JWKS endpoint |
Example: Symmetric Configuration (Internal Auth)
#![allow(unused)]
fn main() {
use schemabound::auth::{JwtConfig, SymmetricJwtConfig};
// Single key for signing and validation
let config = SymmetricJwtConfig::new("your-secret-key-here".into());
// Multiple keys for zero-downtime rotation (last entry signs, earlier entries validate)
let config = SymmetricJwtConfig::with_rotation(vec![
"current_key_abc123".into(),
"previous_key_def456".into(), // still validates until removed
]);
}
Example: Asymmetric Configuration (External IdP)
#![allow(unused)]
fn main() {
use schemabound::auth::{JwtConfig, AsymmetricJwtConfig};
let config = AsymmetricJwtConfig::new(
algorithm: Algorithm::RS256,
private_key_path: "/etc/schemabound/jwt/rsa-private.pem",
);
}
Example: OIDC Discovery (Entra ID, Okta, Keycloak)
#![allow(unused)]
fn main() {
use schemabound::auth::{JwtConfig, OidcJwksConfig};
let config = OidcJwksConfig::new(
issuer_url: "https://login.microsoftonline.com/<tenant-id>/v2.0",
cache_ttl: Duration::from_secs(3600), // refresh public keys every hour
);
}
Rolling Key Rotation (Symmetric Only)
For internal service-to-service auth using shared secrets, SCHEMABOUND supports zero-downtime key rotation via the JwtConfig trait:
- Generate a new signing secret using your preferred secure generator (e.g.,
openssl rand -hex 32) - Add the new key to the front of the rotation list while keeping the old key at the end
- Restart SCHEMABOUND — 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 the rotation list and restart
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 the rotation list, verify:
- The old key is still listed at the end of the rotation vector
- 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 issuer URL matches the IdP’s actual issuer URL (trailing slashes matter)