jwks-cache
High-performance async JWKS cache with ETag revalidation, early refresh, and multi-tenant support — built for modern Rust identity systems.
Table of Contents
- Why jwks-cache?
- Installation
- Quick Start
- Validating Tokens
- Registry Configuration
- Observability
- Persistence & Warm Starts
- Development
- Support
- Acknowledgements
- License
Why jwks-cache?
- HTTP-aware caching: honours
Cache-Control,Expires,ETag, andLast-Modifiedheaders viahttp-cache-semantics, so refresh cadence tracks the upstream contract instead of guessing TTLs. - Resilient refresh loop: background workers use single-flight guards, exponential backoff with jitter, and bounded stale-while-error windows to minimise pressure on identity providers.
- Multi-tenant registry: isolate registrations per tenant, enforce HTTPS, and restrict redirect targets with domain allowlists or SPKI pinning.
- Built-in observability: metrics, traces, and status snapshots are emitted with tenant/provider labels to simplify debugging and SLO tracking.
- Optional persistence: Redis-backed snapshots allow the cache to warm-start without stampeding third-party JWKS endpoints after deploys or restarts.
Installation
Add the crate to your project and enable optional integrations as needed:
# Cargo.toml
[]
# Drop `redis` if persistence is unnecessary.
= { = "0.1", = ["redis"] }
= { = "10.1" }
= { = "0.24" }
= { = "0.12", = ["http2", "json", "rustls-tls", "stream"] }
= { = "0.1" }
= { = "1.48", = ["macros", "rt-multi-thread", "sync", "time"] }
The crate is fully async and designed for the Tokio multi-threaded runtime.
Quick Start
async
Validating Tokens
Use the registry to resolve a kid and build a DecodingKey for jsonwebtoken:
use ;
use Registry;
use Deserialize;
async
The optional third argument to Registry::resolve lets you pass the kid up front, enabling cache hits even when providers rotate keys frequently.
Registry Configuration
Registry keeps tenant/provider state isolated while applying consistent guardrails. The most relevant knobs on IdentityProviderRegistration are:
| Field | Purpose | Default |
|---|---|---|
refresh_early |
Proactive refresh lead time before TTL expiry. | 30s (overridable globally via RegistryBuilder::default_refresh_early) |
stale_while_error |
Serve cached payloads while refreshes fail. | 60s (overridable via default_stale_while_error) |
min_ttl |
Floor applied to upstream cache directives. | 30s |
max_ttl |
Cap applied to upstream TTLs. | 24h |
max_response_bytes |
Maximum JWKS payload size accepted. | 1_048_576 bytes |
negative_cache_ttl |
Optional TTL for failed upstream fetches. | Disabled (0s) |
max_redirects |
Upper bound on HTTP redirects while fetching. | 3 (hard limit 10) |
prefetch_jitter |
Randomised offset applied to refresh scheduling. | 5s |
retry_policy |
Exponential backoff configuration for fetches. | Initial attempt + 2 retries, 250 ms → 2 s backoff, 3 s per attempt, 8 s deadline, full jitter |
pinned_spki |
SHA-256 SPKI fingerprints for TLS pinning. | Empty |
Multi-tenant operations
register/unregisterkeep provider state scoped to each tenant.resolveserves cached JWKS payloads with per-tenant metrics tagging.refreshtriggers an immediate background refresh without waiting for TTL expiry.provider_statusandall_statusesexpose lifecycle state, expiry, error counters, hit rates, and the metrics that powerjwks-cache.openapi.yaml.
Security controls
RegistryBuilder::require_https(true)(default) enforces HTTPS for every registration.- Domain allowlists can be applied globally (
add_allowed_domain) or per registration (allowed_domains). - Provide
pinned_spkivalues (base64 SHA-256) to guard against certificate substitution.
Feature flags
redis: enable Redis-backed snapshots forpersist_allandrestore_from_persistence. When disabled, these methods are cheap no-ops so lifecycle code can stay shared.
Observability
- Metrics emitted via the
metricsfacade includejwks_cache_requests_total,jwks_cache_hits_total,jwks_cache_misses_total,jwks_cache_stale_total,jwks_cache_refresh_total,jwks_cache_refresh_errors_total, and thejwks_cache_refresh_duration_secondshistogram. install_default_exporterinstalls the bundled Prometheus recorder (metrics-exporter-prometheus) and exposes aPrometheusHandlefor HTTP servers to serve/metrics.- Every cache operation is instrumented with
tracingspans keyed by tenant and provider identifiers, making it easy to correlate logs, traces, and metrics.
Persistence & Warm Starts
Enable the redis feature to persist JWKS payloads between deploys:
let registry = builder
.require_https
.add_allowed_domain
.with_redis_client
.build;
// During startup:
registry.restore_from_persistence.await?;
// On graceful shutdown:
registry.persist_all.await?;
Snapshots store the JWKS body, validators, and expiry metadata, keeping cold starts off identity provider rate limits.
Development
cargo fmtcargo clippy --all-targets --all-featurescargo testcargo test --features redis(integration coverage for Redis persistence)
Integration tests rely on wiremock to exercise HTTP caching behaviour, retries, and stale-while-error semantics.
Support Me
If you find this project helpful and would like to support its development, you can buy me a coffee!
Your support is greatly appreciated and motivates me to keep improving this project.
- Fiat
- Crypto
- Bitcoin
bc1pedlrf67ss52md29qqkzr2avma6ghyrt4jx9ecp9457qsl75x247sqcp43c
- Ethereum
0x3e25247CfF03F99a7D83b28F207112234feE73a6
- Polkadot
156HGo9setPcU2qhFMVWLkcmtCEGySLwNqa3DaEiYSWtte4Y
- Bitcoin
Thank you for your support!
Appreciation
We would like to extend our heartfelt gratitude to the following projects and contributors:
Grateful for the Rust community and the maintainers of reqwest, http-cache-semantics, metrics, redis, and tracing, whose work makes this cache possible.
Additional Acknowledgements
- TODO
License
Licensed under GPL-3.0.