AuthOS applies in-process token-bucket limits to the most abuse-sensitive route
groups. Limits are keyed by the trusted client IP derived by
TrustedClientIpKeyExtractor; configure trusted proxy handling correctly before
placing the API behind a load balancer. Do not trust arbitrary forwarded headers
from direct clients.
Route-group limits
| Group | Routes | Refill interval | Burst |
|---|---|---|---|
| Authentication | /auth/* and public /api/auth/* routes assembled by auth_routes |
1 second | 20 |
| Device authorization | /auth/device/code, /auth/device/verify, /auth/token |
5 seconds | 10 |
| Authenticated MFA management | /api/user/mfa/status, /setup, /verify, disable, backup-code regeneration |
300 ms | 5 |
| Login/SAML MFA verification | /api/auth/mfa/verify, /saml/mfa/verify |
1 second | 3 |
These values are source defaults, not a quota promise for every deployment.
Operators can disable the route layers with DISABLE_RATE_LIMITING=true for
isolated tests; never do that on an internet-facing deployment.
Email-generating password, verification, and magic-link handlers also use a separate in-memory throttle: at most five attempts per hour for the normalized email key, partitioned by organization where tenant context is available. MFA middleware also defines a five-attempt, 15-minute IP/user limiter for flows that attach it.
Handling 429
Treat 429 Too Many Requests as temporary. Honor Retry-After when present;
otherwise use bounded exponential backoff with jitter. Retry only idempotent
reads or operations whose idempotency you control—blindly replaying invitation,
token, or billing writes can duplicate work.
async function getWithBackoff(url, options = {}, attempts = 3) {
for (let attempt = 0; attempt < attempts; attempt += 1) {
const response = await fetch(url, options);
if (response.status !== 429) return response;
const retryAfter = Number(response.headers.get('retry-after'));
const baseMs = Number.isFinite(retryAfter)
? retryAfter * 1000
: Math.min(30_000, 500 * 2 ** attempt);
const jitterMs = Math.floor(Math.random() * 250);
await new Promise(resolve => setTimeout(resolve, baseMs + jitterMs));
}
throw new Error('AuthOS rate limit retry budget exhausted');
}
Keep concurrency bounded, cache safe reads, and prefer signed webhooks over polling. Because the built-in counters are process-local, multi-replica deployments must add an edge or shared distributed limiter when they require a strict platform-wide ceiling.
Monitor 429 rates without logging passwords, authorization headers, one-time
tokens, or request bodies containing secrets.