Rate Limit
Faire Auth includes a built-in rate limiter to help manage traffic and prevent abuse. By default, in production mode, the rate limiter is set to:
- Window: 10 seconds
- Max Requests: 100 requests
Server-side requests made using auth.api aren't affected by rate limiting. Rate limits only apply to client-initiated requests.
You can easily customize these settings by passing the rateLimit object to the faireAuth function.
import { faireAuth } from "faire-auth";
export const auth = faireAuth({
rateLimit: {
window: 10, // time window in seconds
max: 100, // max requests in the window
},
})Rate limiting is disabled in development mode by default. In order to enable it, set enabled to true:
export const auth = faireAuth({
rateLimit: {
enabled: true,
//...other options
},
})In addition to the default settings, Faire Auth enforces stricter built-in rules for sensitive paths:
/sign-in/*: 3 requests per 10 seconds/sign-up/*: 3 requests per 10 seconds/change-password/*: 3 requests per 10 seconds/change-email/*: 3 requests per 10 seconds
Plugins can also define their own rate limit rules that are checked before user-defined custom rules.
These built-in rules ensure sensitive operations are protected with stricter limits regardless of your global settings.
Configuring Rate Limit
Connecting IP Address
Rate limiting uses the connecting IP address to track the number of requests made by a user. The
default header checked is x-forwarded-for, which is commonly used in production environments. If
you are using a different header to track the user's IP address, you'll need to specify it.
export const auth = faireAuth({
//...other options
advanced: {
ipAddress: {
ipAddressHeaders: ["cf-connecting-ip"], // Cloudflare specific header example
},
},
rateLimit: {
enabled: true,
window: 60, // time window in seconds
max: 100, // max requests in the window
},
})Rate Limit Window
import { faireAuth } from "faire-auth";
export const auth = faireAuth({
//...other options
rateLimit: {
window: 60, // time window in seconds
max: 100, // max requests in the window
},
})Custom Rules
Custom rules override the default and built-in limits for specific paths. Each rule can be one of three forms:
- Static object — fixed
windowandmax false— disable rate limiting for this path entirely- Function — receives the
HonoRequestand returns{ window, max },false, or a promise of either
Paths support wildcard matching with * (e.g. /two-factor/* matches /two-factor/verify, /two-factor/enable, etc.).
import { faireAuth } from "faire-auth";
export const auth = faireAuth({
//...other options
rateLimit: {
window: 10,
max: 100,
customRules: {
// Static: fixed limits
"/sign-in/email": {
window: 10,
max: 3,
},
// Wildcard: matches any path under /two-factor/
"/two-factor/*": {
window: 10,
max: 3,
},
// Disable: skip rate limiting entirely
"/get-session": false,
// Dynamic: function receives HonoRequest, can be async
"/sign-up/email": async (request) => {
const ip = request.header("x-forwarded-for");
if (isAllowlisted(ip)) return false; // skip for allowlisted IPs
return { window: 30, max: 5 };
},
},
},
})Custom rules are checked after built-in special rules and plugin rules. If a custom rule matches, it overrides any previous match. The middleware matches paths using exact match first, then wildcard matching.
Storage
By default, rate limit data is stored in memory, which may not be suitable for many use cases, particularly in serverless environments. To address this, you can use a database, secondary storage, or custom storage for storing rate limit data.
Using Database
import { faireAuth } from "faire-auth";
export const auth = faireAuth({
//...other options
rateLimit: {
storage: "database",
modelName: "rateLimit", //optional by default "rateLimit" is used
},
})Make sure to run migrate to create the rate limit table in your database.
npx @faire-auth/cli migrateUsing Secondary Storage
If a Secondary Storage has been configured you can use that to store rate limit data.
import { faireAuth } from "faire-auth";
export const auth = faireAuth({
//...other options
rateLimit: {
storage: "secondary-storage"
},
})Custom Storage
If none of the above solutions suits your use case you can implement a customStorage.
import { faireAuth } from "faire-auth";
export const auth = faireAuth({
//...other options
rateLimit: {
customStorage: {
get: async (key) => {
// get rate limit data
},
set: async (key, value) => {
// set rate limit data
},
},
},
})Handling Rate Limit Errors
When a request exceeds the rate limit, Faire Auth returns the following header:
X-Retry-After: The number of seconds until the user can make another request.
To handle rate limit errors on the client side, you can manage them either globally or on a per-request basis. Since Faire Auth clients wrap over Better Fetch, you can pass fetchOptions to handle rate limit errors
Global Handling
import { createAuthClient } from "faire-auth/client";
export const authClient = createAuthClient()({
fetchOptions: {
onError: async (context) => {
const { response } = context;
if (response.status === 429) {
const retryAfter = response.headers.get("X-Retry-After");
console.log(`Rate limit exceeded. Retry after ${retryAfter} seconds`);
}
},
}
})Per Request Handling
import { authClient } from "./auth-client";
await authClient.signIn.email.$post(
{
json: {
email: "email@example.com",
password: "password",
},
},
{
fetchOptions: {
onError: async (context) => {
const { response } = context;
if (response.status === 429) {
const retryAfter = response.headers.get("X-Retry-After");
console.log(`Rate limit exceeded. Retry after ${retryAfter} seconds`);
}
},
},
},
)Schema
If you are using a database to store rate limit data you need this schema:
Table Name: rateLimit
| Field Name | Type | Key | Description |
|---|---|---|---|
| id | string | Database ID | |
| key | string | - | Unique identifier for each rate limit key |
| count | integer | - | Number of requests made in the current window |
| lastRequest | bigint | - | Timestamp of the last request (milliseconds) |