All Docs
FeaturesCalmony Sanctions MonitorUpdated March 12, 2026

Rate Limiting on Authentication Pages (SEC-14)

Rate Limiting on Authentication Pages (SEC-14)

As of v0.1.138, the /sign-in and /sign-up page routes are protected by the same auth rate limit tier that already covered the /api/auth/* API endpoints.

Background

The Auth.js API callbacks at /api/auth/* have been rate-limited since earlier releases (SEC-08): each IP address is allowed a maximum of 10 requests per 60-second sliding window. However, the browser-facing /sign-in and /sign-up pages themselves were classified as public routes and received no rate limiting. An attacker could use those pages to drive repeated form submissions — and therefore repeated calls to the underlying API callbacks — without being throttled at the page layer.

SEC-14 closes this gap by extending rate limiting to the page routes so that both layers share the same per-IP counter.

How it works

Shared rate limit tier

All of the following paths now map to RATE_LIMITS.auth:

Path patternType
/api/auth/*Auth.js callbacks, session, CSRF
/sign-in and /sign-in/*Sign-in page
/sign-up and /sign-up/*Sign-up / account creation page

The 60-second sliding window is shared across all these paths per IP address. A burst of requests that hits both the page routes and the API routes will exhaust the same counter.

Limit: 10 requests / 60 seconds per IP address.

Response behaviour

The response when the limit is exceeded differs by route type:

Route typeRate limit response
/api/* pathsHTTP 429 JSON body with Retry-After and X-RateLimit-* headers
Page paths (/sign-in, /sign-up)HTTP 302 redirect to the same page with ?error=rate_limited&retryAfter=N

Browser-facing pages receive a redirect rather than a raw JSON 429 because browsers render the page response, not the API body. The redirect allows the page to display a human-readable error message.

User-facing error message

When /sign-in is loaded with ?error=rate_limited in the URL, an accessible alert banner is shown:

Too many sign-in attempts Please wait 60 seconds before trying again.

The banner uses role="alert" and aria-live="polite" so it is announced by screen readers.

The retryAfter value in the query parameter reflects the actual number of seconds remaining until the window resets for that IP.

Response headers

All rate-limited responses — whether a redirect or a JSON 429 — include the following headers:

HeaderDescription
Retry-AfterSeconds until the rate limit window resets
X-RateLimit-LimitMaximum requests allowed in the window
X-RateLimit-RemainingRequests remaining in the current window
X-RateLimit-ResetUnix timestamp when the window resets

Security posture

With this change, credential stuffing attacks against the sign-in and sign-up pages are throttled at the middleware layer — before any authentication logic or database query is executed — in the same way that OAuth token harvesting attacks against /api/auth/* have been throttled since SEC-08.

Security headers (CSP, Strict-Transport-Security, X-Content-Type-Options, etc.) are also applied to rate-limit redirect responses, not only to normal page responses.