Security & Rate Limiting
Rate limiting, audit logging, CORS, and JWKS key rotation, all owner-configurable where it makes sense.
Rate limiting
@fastify/rate-limit is registered globally with tiered limits: a default of 100 requests/minute per IP, and a tighter 30/minute for /oauth2/token, /oauth2/introspect, /oauth2/revoke, and /oauth2/register, 60/minute for /oauth2/authorize.
Every OAuth2 endpoint except /oauth2/userinfo is keyed by client_id, not just IP — one noisy integrator can't exhaust the pool other integrators share. /oauth2/userinfo falls back to IP-keying, since it only carries a bearer token, not a client_id. Over the limit, requests get a 429 with Retry-After and RateLimit-* headers.
Configurable from Settings
An owner can adjust the default/OAuth token/OAuth authorize thresholds, or disable rate limiting entirely, from the Authentication Settings page — no redeploy needed.
Audit logging
A dedicated security_events collection (90-day TTL) records every security-relevant action: OAuth token issuance/revocation, consent grant/denial, client registration/revocation, secret rotation, failed sign-ins, member bans/deactivations/ deletions, new API keys, new OAuth apps, and unused-key flags. It's deliberately separate from the shorter-lived auth_events collection (7-day TTL) that backs the dashboard's "recent activity" feed — an audit trail needs longer retention than a UI feed.
A curated subset of these events — bans, deactivations, deletions, new keys, new apps, revocations, secret rotations, unused-key flags — surfaces in the dashboard's notification bell. The rest (token issuance, consent, client registration) stay audit-only and aren't shown there, to keep the feed signal over noise.
CORS
A browser-JS token exchange from a registered origin is allowed by checking both an API client's allowedOrigins and any OAuth application's registered redirectUris — two different entities with different lifecycles, both consulted.
JWKS key rotation
Signing keys rotate automatically on a 90-day interval, with a 30-day grace period during which the previous key is still served at /api/auth/jwks so in-flight tokens don't suddenly fail verification. There's no proactive rotation notification by design — poll the JWKS endpoint rather than caching a key indefinitely.
If a signing key is compromised
See documentation/SECURITY.md's incident-response runbook — broadly: identify the current kid from the live JWKS response, then either force-expire that key's document to trigger early rotation, or delete it outright for a confirmed active-forgery scenario (skipping the grace period). Rotating FORGE_BETTER_AUTH_SECRET itself is a much bigger operation — it encrypts every key's private half at rest.
HTTPS & cookies
- redirect_uris are required to be https, except loopback addresses for native apps (RFC 8252).
- Session cookies are HttpOnly, with Secure derived from the request scheme.
- SameSite is Lax, not Strict — Strict would break "Sign in with Forge" itself, since the flow depends on a cross-site top-level navigation (a sister app redirecting to /oauth2/authorize) carrying the existing Forge session cookie.