Skip to content

Count auth rate limits in the database - #759

Open
jrhizor wants to merge 1 commit into
mainfrom
rate-limit-database-storage
Open

jrhizor wants to merge 1 commit into
mainfrom
rate-limit-database-storage

Conversation

@jrhizor

@jrhizor jrhizor commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

createAuth never configured rateLimit, so better-auth used its defaults: enabled in production, storage: "memory".

That store is a module-scope Map in the app process. On a serverless platform every cold start begins with an empty counter and instances never see each other's, so the limits better-auth ships were counting close to nothing:

Paths Window Max
/sign-in*, /sign-up*, /change-password*, /change-email* 10s 3
/request-password-reset, /send-verification-email, /forget-password* 60s 3

Those windows are fine — the storage behind them was the problem. This switches it to storage: "database", which counts in Postgres with an atomic guarded update (incrementOne, implemented by the drizzle adapter).

Changes

  • rateLimit: { storage: "database" } in the shared auth factory. No custom rules — better-auth's own windows are what we want, they just need somewhere durable to count.
  • rate_limit table, emitted by the better-auth schema generator (it only adds the table once storage is set) and its migration, 0021_rate_limit.sql.
  • Restores the apikey.metadata warning in generate-auth-schema.sh's header. The committed schema-auth.ts had it but the script did not, so regenerating dropped it — as it did here before I put it back.

enabled is left at better-auth's default of "production only", so local dev is unaffected.

Deploying

Needs the migration applied before the app starts. The self-hosted compose already orders this correctly — db-migrate is an init container that web and worker depends_on: service_completed_successfully. Cloud needs the migration in the deploy step ahead of the rollout; on an instance where rate_limit is missing, better-auth's rate limiter has no table to count in.

Verification

drizzle-kit generate produced only the new table — no drift in the rest of the snapshot. Lint and tsc --noEmit pass on packages/lib. Not exercised against a live database; I did not run migrations.


Open workspace in Conductor

better-auth defaults to an in-process memory store, which on a serverless
platform means each cold start begins with an empty counter and instances
never see each other's. The built-in limits it ships — 3 sign-in attempts
per 10s, 3 reset or verification mails per 60s — were counting almost
nothing as a result.

Adds the rate_limit table the storage backend reads, which the schema
generator emits once storage is set.

Also restores the apikey.metadata warning in the generator's own header,
so the next regeneration stops dropping it.
@vercel

vercel Bot commented Sep 15, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
elmo Ready Ready Preview Sep 15, 2026 12:53am UTC

Request Review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant