docs/content/docs/concepts/cookies.mdx
Cookies are used to store data such as session tokens, session data, OAuth state, and more. All cookies are signed using the secret key provided in the auth options or the BETTER_AUTH_SECRET environment variable. If you use versioned secrets for rotation, encrypted cookie data (such as JWE session caches) will automatically use the current key and remain decryptable with previous keys.
By default, Better Auth cookies follow the format ${prefix}.${cookie_name}. The default prefix is "better-auth". You can change the prefix by setting cookiePrefix in the advanced object of the auth options.
import { betterAuth } from "better-auth"
export const auth = betterAuth({
advanced: {
cookiePrefix: "my-app"
}
})
All cookies are httpOnly and secure when the server is running in production mode.
If you want to set custom cookie names and attributes, you can do so by setting cookieOptions in the advanced object of the auth options.
By default, Better Auth uses the following cookies:
session_token to store the session tokensession_data to store the session data if cookie cache is enableddont_remember to store the flag when rememberMe is disabledPlugins may also use cookies to store data. For example, the Two Factor Authentication plugin uses the two_factor cookie to store the two-factor authentication state.
import { betterAuth } from "better-auth"
export const auth = betterAuth({
advanced: {
cookies: {
session_token: {
name: "custom_session_token",
attributes: {
// Set custom cookie attributes
}
},
}
}
})
Sometimes you may need to share cookies across subdomains.
For example, if you authenticate on auth.example.com, you may also want to access the same session on app.example.com.
app.example.com instead of .example.com)status.company.com vs app.company.com)
</Callout>
import { betterAuth } from "better-auth"
export const auth = betterAuth({
advanced: {
crossSubDomainCookies: {
enabled: true,
domain: "app.example.com", // your domain
},
},
trustedOrigins: [
'https://example.com',
'https://app1.example.com',
'https://app2.example.com',
],
})
By default, cookies are secure only when the server is running in production mode. You can force cookies to be always secure by setting useSecureCookies to true in the advanced object in the auth options.
import { betterAuth } from "better-auth"
export const auth = betterAuth({
advanced: {
useSecureCookies: true
}
})
Safari includes a privacy feature called Intelligent Tracking Prevention (ITP) that blocks third-party cookies.
If your Better Auth API is hosted on a different domain than your frontend, Safari may block authentication cookies entirely.
Example that breaks in Safari:
Frontend: https://app.domainB.com
API: https://domainA.com
If your frontend makes a request like:
fetch("https://domainA.com/api/auth/get-session", {
credentials: "include"
})
Safari treats domainA.com as a third-party and blocks its cookies. Which can result in sessions not persisting,
Set-Cookie being ignored, users appearing logged out after login or auth working in Chrome but failing in Safari.
To solve this, there are two solutions:
Instead of calling your API directly, you can proxy it through the same domain as your frontend.
For example, instead of calling:
https://domainA.com/api/auth/*
you can call:
https://app.domainA.com/api/auth/*
Then configure your hosting provider to proxy the request to your actual API server.
This makes the request appear first-party to Safari, allowing cookies to function correctly.
[[redirects]]
from = "/api/*"
to = "https://domainA.com/api/:splat"
status = 200
force = true
{
"rewrites": [
{
"source": "/api/:path*",
"destination": "https://domainA.com/api/:path*"
}
]
}
You can also use a shared parent domain to allow cookies to be shared across subdomains:
https://app.example.com
https://api.example.com
Then, enable cross-subdomain cookies with the domain:
import { betterAuth } from "better-auth"
export const auth = betterAuth({
advanced: {
crossSubDomainCookies: {
enabled: true,
domain: "example.com",
},
},
})
Learn more about cross-subdomain cookies in the documentation above.
This avoids Safari treating the API as third-party.