$npx -y skills add better-auth/skills --skill securityConfigure rate limiting, manage auth secrets, set up CSRF protection, define trusted origins, secure sessions and cookies, encrypt OAuth tokens, track IP addresses, and implement audit logging for Better Auth. Use when users need to secure their auth setup, prevent brute force at
| 1 | ## Secret Management |
| 2 | |
| 3 | ### Configuring the Secret |
| 4 | |
| 5 | ```ts |
| 6 | import { betterAuth } from "better-auth"; |
| 7 | |
| 8 | export const auth = betterAuth({ |
| 9 | secret: process.env.BETTER_AUTH_SECRET, // or via `BETTER_AUTH_SECRET` env |
| 10 | }); |
| 11 | ``` |
| 12 | |
| 13 | Better Auth looks for secrets in this order: |
| 14 | 1. `options.secret` in your config |
| 15 | 2. `BETTER_AUTH_SECRET` environment variable |
| 16 | 3. `AUTH_SECRET` environment variable |
| 17 | |
| 18 | ### Secret Requirements |
| 19 | |
| 20 | - Rejects default/placeholder secrets in production |
| 21 | - Warns if shorter than 32 characters or entropy below 120 bits |
| 22 | - Generate: `openssl rand -base64 32` |
| 23 | - Never commit secrets to version control |
| 24 | |
| 25 | ## Rate Limiting |
| 26 | |
| 27 | Enabled in production by default. Applies to all endpoints. Plugins can override per-endpoint. |
| 28 | |
| 29 | ### Default Configuration |
| 30 | |
| 31 | ```ts |
| 32 | import { betterAuth } from "better-auth"; |
| 33 | |
| 34 | export const auth = betterAuth({ |
| 35 | rateLimit: { |
| 36 | enabled: true, // Default: true in production |
| 37 | window: 10, // Time window in seconds (default: 10) |
| 38 | max: 100, // Max requests per window (default: 100) |
| 39 | }, |
| 40 | }); |
| 41 | ``` |
| 42 | |
| 43 | ### Storage Options |
| 44 | |
| 45 | Options: `"memory"` (resets on restart, avoid on serverless), `"database"` (persistent), `"secondary-storage"` (Redis, default when available). |
| 46 | |
| 47 | ```ts |
| 48 | rateLimit: { |
| 49 | storage: "database", |
| 50 | } |
| 51 | ``` |
| 52 | |
| 53 | ### Custom Storage |
| 54 | |
| 55 | Implement your own rate limit storage: |
| 56 | |
| 57 | ```ts |
| 58 | rateLimit: { |
| 59 | customStorage: { |
| 60 | get: async (key) => { |
| 61 | // Return { count: number, expiresAt: number } or null |
| 62 | }, |
| 63 | set: async (key, data) => { |
| 64 | // Store the rate limit data |
| 65 | }, |
| 66 | }, |
| 67 | } |
| 68 | ``` |
| 69 | |
| 70 | ### Per-Endpoint Rules |
| 71 | |
| 72 | Sensitive endpoints default to 3 requests per 10 seconds (`/sign-in`, `/sign-up`, `/change-password`, `/change-email`). Override: |
| 73 | |
| 74 | ```ts |
| 75 | rateLimit: { |
| 76 | customRules: { |
| 77 | "/api/auth/sign-in/email": { |
| 78 | window: 60, // 1 minute window |
| 79 | max: 5, // 5 attempts |
| 80 | }, |
| 81 | "/api/auth/some-safe-endpoint": false, // Disable rate limiting |
| 82 | }, |
| 83 | } |
| 84 | ``` |
| 85 | |
| 86 | ## CSRF Protection |
| 87 | |
| 88 | Multi-layer protection: origin header validation, Fetch Metadata checks, and first-login protection. |
| 89 | |
| 90 | ### Configuration |
| 91 | |
| 92 | ```ts |
| 93 | import { betterAuth } from "better-auth"; |
| 94 | |
| 95 | export const auth = betterAuth({ |
| 96 | advanced: { |
| 97 | disableCSRFCheck: false, // Default: false (keep enabled) |
| 98 | }, |
| 99 | }); |
| 100 | ``` |
| 101 | |
| 102 | Only disable for testing or with an alternative CSRF mechanism. |
| 103 | |
| 104 | ## Trusted Origins |
| 105 | |
| 106 | ### Configuring Trusted Origins |
| 107 | |
| 108 | ```ts |
| 109 | import { betterAuth } from "better-auth"; |
| 110 | |
| 111 | export const auth = betterAuth({ |
| 112 | baseURL: "https://api.example.com", |
| 113 | trustedOrigins: [ |
| 114 | "https://app.example.com", |
| 115 | "https://admin.example.com", |
| 116 | ], |
| 117 | }); |
| 118 | ``` |
| 119 | |
| 120 | The `baseURL` origin is automatically trusted. Also configurable via env: `BETTER_AUTH_TRUSTED_ORIGINS=https://app.example.com,https://admin.example.com` |
| 121 | |
| 122 | ### Wildcard Patterns |
| 123 | |
| 124 | ```ts |
| 125 | trustedOrigins: [ |
| 126 | "*.example.com", // Matches any subdomain |
| 127 | "https://*.example.com", // Protocol-specific wildcard |
| 128 | "exp://192.168.*.*:*/*", // Custom schemes (e.g., Expo) |
| 129 | ] |
| 130 | ``` |
| 131 | |
| 132 | ### Dynamic Trusted Origins |
| 133 | |
| 134 | Compute trusted origins based on the request: |
| 135 | |
| 136 | ```ts |
| 137 | trustedOrigins: async (request) => { |
| 138 | // Validate against database, header, etc. |
| 139 | const tenant = getTenantFromRequest(request); |
| 140 | return [`https://${tenant}.myapp.com`]; |
| 141 | } |
| 142 | ``` |
| 143 | |
| 144 | Validates `callbackURL`, `redirectTo`, `errorCallbackURL`, `newUserCallbackURL`, and `origin` against trusted origins. Invalid URLs receive 403. |
| 145 | |
| 146 | ## Session Security |
| 147 | |
| 148 | ### Session Expiration |
| 149 | |
| 150 | ```ts |
| 151 | import { betterAuth } from "better-auth"; |
| 152 | |
| 153 | export const auth = betterAuth({ |
| 154 | session: { |
| 155 | expiresIn: 60 * 60 * 24 * 7, // 7 days (default) |
| 156 | updateAge: 60 * 60 * 24, // Refresh session every 24 hours (default) |
| 157 | }, |
| 158 | }); |
| 159 | ``` |
| 160 | |
| 161 | ### Session Caching Strategies |
| 162 | |
| 163 | Cache session data in cookies to reduce database queries: |
| 164 | |
| 165 | ```ts |
| 166 | session: { |
| 167 | cookieCache: { |
| 168 | enabled: true, |
| 169 | maxAge: 60 * 5, // 5 minutes |
| 170 | strategy: "compact", // Options: "compact", "jwt", "jwe" |
| 171 | }, |
| 172 | } |
| 173 | ``` |
| 174 | |
| 175 | Strategies: `"compact"` (Base64url + HMAC, smallest), `"jwt"` (HS256, standard), `"jwe"` (encrypted, use when session has sensitive data). |
| 176 | |
| 177 | ## Cookie Security |
| 178 | |
| 179 | Defaults: `secure: true` (HTTPS/production), `sameSite: "lax"`, `httpOnly: true`, `path: "/"`, prefix `__Secure-`. |
| 180 | |
| 181 | ### Custom Cookie Configuration |
| 182 | |
| 183 | ```ts |
| 184 | import { betterAuth } from "better-auth"; |
| 185 | |
| 186 | export const auth = betterAuth({ |
| 187 | advanced: { |
| 188 | useSecureCookies: true, // Force secure cookies |
| 189 | cookiePrefix: "myapp", // Custom prefix (default: "better-auth") |
| 190 | defaultCookieAttributes: { |
| 191 | sameSite: "strict", // Stricter CSRF protection |
| 192 | path: "/auth", // Limit cookie scope |
| 193 | }, |
| 194 | }, |
| 195 | }); |
| 196 | ``` |
| 197 | |
| 198 | ### Cross-Subdomain Cookies |
| 199 | |
| 200 | ```ts |
| 201 | advanced: { |
| 202 | crossSubDomainCookies: { |
| 203 | enabled: true, |