[GH-ISSUE #6035] API Key rate limiting not behaving as expected #10406

Open
opened 2026-04-13 06:31:22 -05:00 by GiteaMirror · 4 comments
Owner

Originally created by @xaviemirmon on GitHub (Nov 17, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/6035

Originally assigned to: @ping-maxwell on GitHub.

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

We've implemented rate limiting on our app but it's not behaving how we'd expect.

Because it's based on last_request_at it waits until the number of requests has been reached then will wait the time set and refresh

The docs are confusing as it make it appear that every day the user has 10 requests per day. In reality they have 10 requests but have to wait a day from the last request for it to reset.

We're trying to implement 1000 per hour limit but it doesn't work with the current rate limit behaviour. How do we get it so that it resets once per hour?

Current vs. Expected behavior

Expected that rate limit resets rateLimitMax to amount after timeWindow has elapsed from first request for that period.

What version of Better Auth are you using?

1.3.7

System info

{
  "system": {
    "platform": "linux",
    "arch": "x64",
    "version": "#35-Ubuntu SMP PREEMPT_DYNAMIC Sat Oct 11 10:06:31 UTC 2025",
    "release": "6.14.0-35-generic",
    "cpuCount": 2,
    "cpuModel": "Common KVM processor",
    "totalMemory": "7.75 GB",
    "freeMemory": "6.62 GB"
  },
  "node": {
    "version": "v22.21.0",
    "env": "development"
  },
  "packageManager": {
    "name": "npm",
    "version": "10.9.4"
  },
  "frameworks": null,
  "databases": null,
  "betterAuth": {
    "version": "Unknown",
    "config": null
  }
}

Which area(s) are affected? (Select all that apply)

Package

Auth config (if applicable)


Additional context

No response

Originally created by @xaviemirmon on GitHub (Nov 17, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/6035 Originally assigned to: @ping-maxwell on GitHub. ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce We've implemented rate limiting on our app but it's not behaving how we'd expect. Because it's based on last_request_at it waits until the number of requests has been reached then will wait the time set and refresh The [docs](https://www.better-auth.com/docs/plugins/api-key#rate-limiting) are confusing as it make it appear that every day the user has 10 requests per day. In reality they have 10 requests but have to wait a day from the last request for it to reset. We're trying to implement 1000 per hour limit but it doesn't work with the current rate limit behaviour. How do we get it so that it resets once per hour? ### Current vs. Expected behavior Expected that rate limit resets `rateLimitMax` to amount after `timeWindow` has elapsed from first request for that period. ### What version of Better Auth are you using? 1.3.7 ### System info ```bash { "system": { "platform": "linux", "arch": "x64", "version": "#35-Ubuntu SMP PREEMPT_DYNAMIC Sat Oct 11 10:06:31 UTC 2025", "release": "6.14.0-35-generic", "cpuCount": 2, "cpuModel": "Common KVM processor", "totalMemory": "7.75 GB", "freeMemory": "6.62 GB" }, "node": { "version": "v22.21.0", "env": "development" }, "packageManager": { "name": "npm", "version": "10.9.4" }, "frameworks": null, "databases": null, "betterAuth": { "version": "Unknown", "config": null } } ``` ### Which area(s) are affected? (Select all that apply) Package ### Auth config (if applicable) ```typescript ``` ### Additional context _No response_
GiteaMirror added the securitybug labels 2026-04-13 06:31:23 -05:00
Author
Owner

@better-auth-agent[bot] commented on GitHub (Nov 17, 2025):

It sounds like what you’re seeing is due to our rate limiting logic using the time of your most recent request to decide when the counter resets rather than anchoring it to the start of a fixed window. Currently, when you set a limit (like 10 per day), each time a request is made the window “slides” forward—so if a user makes a request near the end of the window period, the cooldown extends until a full period from that last request has passed.

For a fixed 1‑hour window (i.e. 1000 requests per hour that resets on the hour, not relative to the last request), you’d need to adjust your approach or use an alternate strategy. One option is to configure your rate limiting mechanism to record the start time of the window on the very first request and then base the reset on that timestamp. If the current plugin or built‑in logic doesn’t offer that configuration, it might be something to make a feature request for.

Could you let me know if you’ve already tried adjusting your configuration or if you’re open to exploring a custom middleware solution to implement a fixed window? Tag @better-auth-agent if you would like an answer from the Agent.

DiagramDiscordGitHub

Diagram Join Star

<!-- gh-comment-id:3541171809 --> @better-auth-agent[bot] commented on GitHub (Nov 17, 2025): It sounds like what you’re seeing is due to our rate limiting logic using the time of your most recent request to decide when the counter resets rather than anchoring it to the start of a fixed window. Currently, when you set a limit (like 10 per day), each time a request is made the window “slides” forward—so if a user makes a request near the end of the window period, the cooldown extends until a full period from that last request has passed. For a fixed 1‑hour window (i.e. 1000 requests per hour that resets on the hour, not relative to the last request), you’d need to adjust your approach or use an alternate strategy. One option is to configure your rate limiting mechanism to record the start time of the window on the very first request and then base the reset on that timestamp. If the current plugin or built‑in logic doesn’t offer that configuration, it might be something to make a feature request for. Could you let me know if you’ve already tried adjusting your configuration or if you’re open to exploring a custom middleware solution to implement a fixed window? Tag @better-auth-agent if you would like an answer from the Agent. <!-- bot:webhook reply v1 --> [Diagram](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) • [Discord](https://discord.gg/better-auth) • [GitHub](https://github.com/better-auth/better-auth) [![Diagram](https://img.shields.io/badge/Diagram-2b3137?style=flat-square)](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) [![Join](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&style=flat-square)](https://discord.gg/better-auth) [![Star](https://img.shields.io/badge/star-181717?logo=github&logoColor=white&style=flat-square)](https://github.com/better-auth/better-auth)
Author
Owner

@dosubot[bot] commented on GitHub (Nov 17, 2025):

Hi @xaviemirmon! I'm Dosu and I’m helping the better-auth team.

The current API key rate limiting in Better Auth v1.3.7 uses a rolling window based on the last request time, not a fixed window. After you hit the request limit, you must wait for the full time window (e.g., one hour) from your last request before the limit resets. This means you can't configure it to reset every hour on the hour—only after the time window has elapsed since the last request, which matches what you're seeing and is confirmed in the latest documentation update (source).

There is no built-in support for fixed window rate limiting (like "1000 requests per hour, resetting at the top of each hour"). If you need that behavior, you'll need to implement custom logic outside of Better Auth, such as using an external rate limiter or a scheduled reset in your storage layer.

The documentation can be misleading, as it suggests a daily or hourly reset, but the actual implementation is rolling and based on the last request (source). The refillInterval and refillAmount settings are not enforced as a leaky-bucket or steady refill mechanism—they only affect the "remaining" count, not the main rate limit window (source).

If you want true fixed window rate limiting, you'll need to build a workaround or use a different rate limiting strategy alongside Better Auth.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Chat with Dosu Join Discord Share on X

<!-- gh-comment-id:3541205778 --> @dosubot[bot] commented on GitHub (Nov 17, 2025): <!-- Greeting --> Hi @xaviemirmon! I'm [Dosu](https://go.dosu.dev/dosubot) and I’m helping the better-auth team. <!-- Answer --> The current API key rate limiting in Better Auth v1.3.7 uses a rolling window based on the last request time, not a fixed window. After you hit the request limit, you must wait for the full time window (e.g., one hour) from your last request before the limit resets. This means you can't configure it to reset every hour on the hour—only after the time window has elapsed since the last request, which matches what you're seeing and is confirmed in the latest documentation update ([source](https://github.com/better-auth/better-auth/pull/5950)). There is no built-in support for fixed window rate limiting (like "1000 requests per hour, resetting at the top of each hour"). If you need that behavior, you'll need to implement custom logic outside of Better Auth, such as using an external rate limiter or a scheduled reset in your storage layer. The documentation can be misleading, as it suggests a daily or hourly reset, but the actual implementation is rolling and based on the last request ([source](https://github.com/better-auth/better-auth/blob/d768852258d1fea921984196806608af2983a867/packages/better-auth/src/plugins/api-key/rate-limit.ts#L18-L97)). The refillInterval and refillAmount settings are not enforced as a leaky-bucket or steady refill mechanism—they only affect the "remaining" count, not the main rate limit window ([source](https://github.com/better-auth/better-auth/issues/3573#issuecomment-3111930670)). If you want true fixed window rate limiting, you'll need to build a workaround or use a different rate limiting strategy alongside Better Auth. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/53b3ed7b-7c83-480e-ab28-34d19c390b2d?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/53b3ed7b-7c83-480e-ab28-34d19c390b2d?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/53b3ed7b-7c83-480e-ab28-34d19c390b2d?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/53b3ed7b-7c83-480e-ab28-34d19c390b2d?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/53b3ed7b-7c83-480e-ab28-34d19c390b2d?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/53b3ed7b-7c83-480e-ab28-34d19c390b2d?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/53b3ed7b-7c83-480e-ab28-34d19c390b2d?feedback_type=other)</sup>&nbsp;&nbsp;[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/6035)
Author
Owner

@ping-maxwell commented on GitHub (Nov 17, 2025):

Hey I haven't checked out your situation too deeply just yet, but wanted to mention that I have updated the rate-limit docs for api-keys recently, however it was merged to canary not main so the main better-auth documentation doesn't have the updated version.
Please check out https://canary.better-auth.com/docs/plugins/api-key#rate-limiting and let me know it if answers any questions you may have.

<!-- gh-comment-id:3542994522 --> @ping-maxwell commented on GitHub (Nov 17, 2025): Hey I haven't checked out your situation too deeply just yet, but wanted to mention that I have updated the rate-limit docs for api-keys recently, however it was merged to canary not main so the main better-auth documentation doesn't have the updated version. Please check out https://canary.better-auth.com/docs/plugins/api-key#rate-limiting and let me know it if answers any questions you may have.
Author
Owner

@xaviemirmon commented on GitHub (Nov 18, 2025):

@ping-maxwell Thanks for sharing the updated docs—very helpful! The problem we're facing is that the rate limit isn't "sliding." It seems to be doing something along the lines of the following logic:

if now - lastRequest >= timeWindow:
    reset counter
else
    increment counter

There's nothing tracking the first request so the counts can pile up quite quickly if the timeWindow is set to a large amount.

I'd expect there to be something along these lines for sliding counter:

if now - firstRequest >= timeWindow:
    reset counter
    reset firstRequest = now
else
    increment counter

In essence this bit from the updated docs isn't working correctly because there is nothing tracking when that time window has started:

Within Window: For subsequent requests within the timeWindow, the requestCount is incremented. If requestCount reaches rateLimitMax, the request is rejected with a RATE_LIMITED error code.

<!-- gh-comment-id:3547366471 --> @xaviemirmon commented on GitHub (Nov 18, 2025): @ping-maxwell Thanks for sharing the updated docs—very helpful! The problem we're facing is that the rate limit isn't "sliding." It seems to be doing something along the lines of the following logic: ``` if now - lastRequest >= timeWindow: reset counter else increment counter ``` There's nothing tracking the first request so the counts can pile up quite quickly if the `timeWindow` is set to a large amount. I'd expect there to be something along these lines for sliding counter: ``` if now - firstRequest >= timeWindow: reset counter reset firstRequest = now else increment counter ``` In essence this bit from the updated docs isn't working correctly because there is nothing tracking when that time window has started: > Within Window: For subsequent requests within the timeWindow, the requestCount is incremented. If requestCount reaches rateLimitMax, the request is rejected with a RATE_LIMITED error code.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#10406