docs/content/docs/reference/security.mdx
This page contains information about security features of Better Auth.
Better Auth uses the scrypt algorithm to hash passwords by default. This algorithm is designed to be memory-hard and CPU-intensive, making it resistant to brute-force attacks. You can customize the password hashing function by setting the password option in the configuration. This option should include a hash function to hash passwords and a verify function to verify them.
Better Auth supports non-destructive rotation of BETTER_AUTH_SECRET. When you configure versioned secrets via the secrets option or the BETTER_AUTH_SECRETS environment variable, all new encrypted data includes a key version identifier. Decryption performs a direct key lookup by version — no trial decryption.
Legacy data encrypted before rotation (bare-hex format) is still decryptable using the original BETTER_AUTH_SECRET as a fallback. No database migrations or downtime are required. Data is lazily re-encrypted with the current key when it is next written.
See the secrets option for configuration details.
Better Auth uses secure session management to protect user data. Sessions are stored in the database or a secondary storage, if configured, to prevent unauthorized access. By default, sessions expire after 7 days, but you can customize this value in the configuration. Additionally, each time a session is used, if it reaches the updateAge threshold, the expiration date is extended, which by default is set to 1 day.
Better Auth allows you to revoke sessions to enhance security. When a session is revoked, the user is logged out and can no longer access the application. A logged in user can also revoke their own sessions to log out from different devices or browsers.
See the session management for more details.
Better Auth includes multiple safeguards to prevent Cross-Site Request Forgery (CSRF) attacks:
Avoid simple requests
See Avoiding simple requests for more details. By default, Better Auth prefers non-simple requests for state-changing operations, typically by requiring a non-simple header or a Content-Type header of application/json.
Some routes intentionally accept application/x-www-form-urlencoded requests for progressive enhancement or protocol interoperability, including sign-in/sign-up email flows and some callback/token-style endpoints. Those routes rely on additional protections such as origin validation, Fetch Metadata checks where applicable, and protocol-specific state or nonce validation.
Origin Validation
Each request’s Origin header is verified to confirm it comes from your application or another explicitly trusted source. Requests from untrusted origins are rejected. By default, Better Auth trusts the base URL of your app, but you can specify additional trusted origins via the trustedOrigins configuration option.
Secure Cookie Settings
Session cookies use the SameSite=Lax attribute by default, preventing browsers from sending cookies with most cross-site requests. You can override this behavior using the defaultCookieAttributes option.
Fetch Metadata Protection for First-Login CSRF
Better Auth also uses Fetch Metadata headers (Sec-Fetch-Site, Sec-Fetch-Mode, Sec-Fetch-Dest) to provide additional CSRF protection specifically for first-login scenarios, where the client does not yet have any cookies.
This protection applies to sign-in and sign-up email routes, which accept form submissions (simple requests) to support progressive enhancement patterns.
When a sign-in or sign-up request is received without any cookies:
If the browser indicates a cross-site navigation (for example: Sec-Fetch-Site: cross-site and Sec-Fetch-Mode: navigate), Better Auth blocks the request as a potential login CSRF attack.
Otherwise, if the request carries an Origin or Referer header, Better Auth validates it against your trustedOrigins configuration and rejects untrusted origins. This applies whether or not the client sends Fetch Metadata headers.
Only when the request carries neither Fetch Metadata headers nor an Origin/Referer header (typical of non-browser clients) does Better Auth fall back to permissive behavior, so server-to-server and native clients are not rejected.
This mechanism allows modern browsers to receive stronger protection against first-login CSRF attacks without requiring CSRF tokens or client-side JavaScript, and works seamlessly with progressive enhancement patterns.
<Callout type="info"> Non-browser HTTP clients (mobile apps, server-to-server) that send an `Origin` or `Referer` header must use a value listed in `trustedOrigins`, since Better Auth validates it whenever it is present. Clients that send no origin header and no Fetch Metadata (typical of non-browser tooling) are not affected. </Callout>No Mutations on GET Requests (with additional safeguards)
GET requests are assumed to be read-only and should not alter the application's state. In cases where a GET request must perform a mutation—such as during OAuth callbacks - Better Auth applies extra security measures, including validating nonce and state parameters to ensure the request's authenticity.
Better Auth provides two separate options to disable security checks. These options control different aspects of security:
disableCSRFCheckDisables all CSRF protection, including:
Sec-Fetch-Site, Sec-Fetch-Mode, Sec-Fetch-Dest){
advanced: {
disableCSRFCheck: true
}
}
disableOriginCheckDisables URL validation against trustedOrigins, including:
callbackURL validationredirectTo validationerrorCallbackURL validationnewUserCallbackURL validation{
advanced: {
disableOriginCheck: true
}
}
| Option | What it disables |
|---|---|
disableCSRFCheck | CSRF protection only (origin header validation, Fetch Metadata checks) |
disableOriginCheck | URL validation AND CSRF protection (for backward compatibility) |
To secure OAuth flows, Better Auth stores the OAuth state and PKCE (Proof Key for Code Exchange) using the configured state storage strategy. By default, the state payload is stored in verification storage and the state value is also persisted in a signed cookie for validation. When storeStateStrategy is set to cookie, the full state payload is encrypted into a short-lived cookie instead of creating a verification record.
The state helps prevent CSRF attacks, while PKCE protects against code injection threats. After the OAuth callback is processed, Better Auth expires the relevant cookie and, when verification storage is used, deletes the stored verification record.
Better Auth assigns secure cookies by default when the base URL uses https. These secure cookies are encrypted and only sent over secure connections, adding an extra layer of protection. They are also set with the sameSite attribute to lax by default to prevent cross-site request forgery attacks. And the httpOnly attribute is enabled to prevent client-side JavaScript from accessing the cookie.
For Cross-Subdomain Cookies, you can set the crossSubDomainCookies option in the configuration. This option allows cookies to be shared across subdomains, enabling seamless authentication across multiple subdomains.
You can customize cookie names to minimize the risk of fingerprinting attacks and set specific cookie options as needed for additional control. For more information, refer to the cookie options.
Plugins can also set custom cookie options to align with specific security needs. If you're using Better Auth in non-browser environments, plugins offer ways to manage cookies securely in those contexts as well.
Better Auth includes built-in rate limiting to safeguard against brute-force attacks. Rate limits are applied across all routes by default, with specific routes subject to stricter limits based on potential risk.
Better Auth uses client IP addresses for rate limiting and security monitoring. By default, it reads the IP address from the standard X-Forwarded-For header. However, you can configure a specific trusted header to ensure accurate IP address detection and prevent IP spoofing attacks.
You can configure the IP address header in your Better Auth configuration:
{
advanced: {
ipAddress: {
ipAddressHeaders: ['cf-connecting-ip'] // or any other custom header
}
}
}
This ensures that Better Auth only accepts IP addresses from your trusted proxy's header, making it more difficult for attackers to bypass rate limiting or other IP-based security measures by spoofing headers.
Behind a proxy chain, list your proxies in trustedProxies so Better Auth
resolves the real client instead of the spoofable leftmost X-Forwarded-For
token:
{
advanced: {
ipAddress: {
// your proxies' addresses, not a broad private range that also covers clients
trustedProxies: ['192.0.2.10', '10.0.0.0/24']
}
}
}
If your application runs behind a reverse proxy or load balancer, Better Auth can derive the base URL from the inbound request's X-Forwarded-Host and X-Forwarded-Proto headers. This is useful when your app is accessible from multiple domains (e.g. example.com and app.example.dev) and you don't want to hardcode a single baseURL in the configuration.
When trustedProxyHeaders is enabled and no baseURL is set in the configuration (or via environment variables), Better Auth uses the forwarded headers to construct the base URL on each request. This means OAuth callbacks, email verification links, and redirects will automatically use whichever domain the user loaded the app on.
{
advanced: {
trustedProxyHeaders: true
}
}
When enabled, the base URL resolution follows this priority:
baseURL from the configuration (if set, proxy headers are ignored)BETTER_AUTH_URL, NEXT_PUBLIC_BETTER_AUTH_URL, etc.)X-Forwarded-Host + X-Forwarded-Proto headers (when trustedProxyHeaders is true)If you're serving your app from multiple approved domains, you'll typically want to:
baseURL from the configuration so it's derived per-request from the proxy headerstrustedOrigins to an allowlist of your approved domains (see below)crossSubDomainCookies disabled — cookies are host-only by default, which means each domain gets its own independent sessionTrusted origins prevent CSRF attacks and block open redirects. You can set a list of trusted origins in the trustedOrigins configuration option. Requests from origins not on this list are automatically blocked.
The most basic usage is to specify exact origins, below is an example of a trusted origins configuration:
{
trustedOrigins: [
"https://example.com",
"https://app.example.com",
"http://localhost:3000"
]
}
Better Auth supports wildcard patterns in trusted origins, which allows you to trust multiple subdomains with a single entry:
{
trustedOrigins: [
"*.example.com", // Trust all subdomains of example.com (any protocol)
"https://*.example.com", // Trust only HTTPS subdomains of example.com
"http://*.dev.example.com" // Trust all HTTP subdomains of dev.example.com
]
}
When using a wildcard pattern with a protocol prefix (like https://):
*When using a wildcard pattern without a protocol prefix (like *.example.com):
Trusted origins also support custom schemes for mobile apps and browser extensions:
{
trustedOrigins: [
"myapp://", // Mobile app scheme
"chrome-extension://YOUR_EXTENSION_ID", // Browser extension
"exp://**", // Trust all Expo development URLs
"exp://10.0.0.*:*/**", // Trust 10.0.0.x IP range with any port
]
}
You can also dynamically set the list of trusted origins by providing a function that returns it:
{
trustedOrigins: async (request) => {
const trustedOrigins = await queryTrustedDomains();
return trustedOrigins;
}
}
During sign-in, Better Auth makes server-side requests to OAuth and OIDC provider endpoints: the token exchange, token refresh, token introspection, and key-set (JWKS) requests. These requests do not follow HTTP redirects. A conformant provider answers these endpoints with a direct response and never redirects, so standard integrations are unaffected. Refusing redirects keeps a provider endpoint from steering a server-side request to an unintended internal address.
Better Auth prevents email enumeration on the sign-up and change-email endpoints. When requireEmailVerification is enabled or autoSignIn is set to false, the sign-up endpoint returns the same 200 response whether the email is already registered or not, following OWASP authentication best practices. Timing attacks are mitigated by simulating password hashing on duplicate sign-up attempts.
See Email and Password — Email Enumeration Protection for configuration details.
If you discover a security vulnerability in Better Auth, please report it via GitHub Security Advisories. We address all reports promptly, and credits will be given for validated discoveries.