[PR #8803] feat: enhance api-key rate limit system #16470

Open
opened 2026-04-13 10:32:06 -05:00 by GiteaMirror · 0 comments
Owner

Original Pull Request: https://github.com/better-auth/better-auth/pull/8803

State: open
Merged: No


closes https://github.com/better-auth/better-auth/issues/6035

Summary

Fixes API key rate limiting so it matches documented “N requests per time window” expectations (issue #6035).

Previously, limiting used lastRequest updated on every successful validation, so the effective period slid with activity and tryAgainIn was tied to time since the last successful request. That differed from a fixed window starting at the first request in a period (and from “10 per day” style wording).

What changed

Fixed-window algorithm

  • Each limit is enforced over [rateLimitWindowStart, rateLimitWindowStart + rateLimitTimeWindow).
  • rateLimitWindowStart is set when a window begins and does not advance on each allowed request.
  • requestCount increments within that window; when it reaches rateLimitMax, further validations return RATE_LIMITED until the window ends.
  • tryAgainIn is the time remaining until rateLimitWindowStart + rateLimitTimeWindow (window end), not until “idle for timeWindow since last request.”
  • Window rollover uses elapsed >= rateLimitTimeWindow so the boundary at exactly one window length is handled correctly (replacing the old strict > behavior on “time since last request”).

New / updated data

  • rateLimitWindowStart (Date | null) added to the API key model and plugin schema; null until the first validation in a new period (or after a reset).
  • lastRequest remains for auditing and is still updated on allowed validations when rate limiting applies.
  • Secondary storage: serialize/deserialize rateLimitWindowStart in the API key JSON cache.

Update API key behavior

  • Changing rateLimitEnabled, rateLimitTimeWindow, or rateLimitMax clears rateLimitWindowStart and resets requestCount to 0 so the next validation starts a fresh window under the new settings.

Breaking change

  • Database: add a nullable rateLimitWindowStart column (or equivalent) for the apikey table to match the plugin schema. Existing rows with NULL get a new window on the next successful verification.

Migration / upgrade notes for users

  1. Run your usual Better Auth migration flow so the schema includes rateLimitWindowStart.
  2. Expect different rate-limit timing vs the old behavior (by design): limits are per fixed window from window start, not per rolling idle period from lastRequest.
**Original Pull Request:** https://github.com/better-auth/better-auth/pull/8803 **State:** open **Merged:** No --- closes https://github.com/better-auth/better-auth/issues/6035 ## Summary Fixes API key rate limiting so it matches documented “N requests per time window” expectations ([issue #6035](https://github.com/better-auth/better-auth/issues/6035)). Previously, limiting used `lastRequest` updated on **every** successful validation, so the effective period slid with activity and `tryAgainIn` was tied to time since the **last** successful request. That differed from a **fixed window** starting at the first request in a period (and from “10 per day” style wording). ## What changed ### Fixed-window algorithm - Each limit is enforced over **`[rateLimitWindowStart, rateLimitWindowStart + rateLimitTimeWindow)`**. - **`rateLimitWindowStart`** is set when a window begins and **does not** advance on each allowed request. - **`requestCount`** increments within that window; when it reaches **`rateLimitMax`**, further validations return `RATE_LIMITED` until the window ends. - **`tryAgainIn`** is the time remaining until **`rateLimitWindowStart + rateLimitTimeWindow`** (window end), not until “idle for `timeWindow` since last request.” - Window rollover uses **`elapsed >= rateLimitTimeWindow`** so the boundary at exactly one window length is handled correctly (replacing the old strict `>` behavior on “time since last request”). ### New / updated data - **`rateLimitWindowStart`** (`Date | null`) added to the API key model and plugin schema; `null` until the first validation in a new period (or after a reset). - **`lastRequest`** remains for auditing and is still updated on allowed validations when rate limiting applies. - **Secondary storage**: serialize/deserialize `rateLimitWindowStart` in the API key JSON cache. ### Update API key behavior - Changing **`rateLimitEnabled`**, **`rateLimitTimeWindow`**, or **`rateLimitMax`** clears **`rateLimitWindowStart`** and resets **`requestCount`** to `0` so the next validation starts a fresh window under the new settings. # Breaking change - **Database**: add a nullable **`rateLimitWindowStart`** column (or equivalent) for the **`apikey`** table to match the plugin schema. Existing rows with `NULL` get a new window on the next successful verification. ## Migration / upgrade notes for users 1. Run your usual Better Auth migration flow so the schema includes **`rateLimitWindowStart`**. 2. Expect **different** rate-limit timing vs the old behavior (by design): limits are per **fixed window from window start**, not per rolling idle period from **`lastRequest`**.
GiteaMirror added the pull-request label 2026-04-13 10:32:06 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#16470