new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Better Auth · all subjects

better auth/deployment

61 notes in this subject, read out of this brain and free to use. This is page 1 of 2.

Better Auth self-hosting

Better Auth can be deployed on your own infrastructure with full control.

Better Auth production readiness

Better Auth is production ready and works either embedded in your app or as a dedicated auth server.

Better Auth deployment flexibility

Better Auth can be deployed in two ways: run alongside your app or as a standalone self-hosted auth server.

Configure trustedOrigins for browser extensions

In the Better Auth server configuration, add the extension URL to the trustedOrigins list: betterAuth({ trustedOrigins: ["chrome-extension://YOUR_EXTENSION_ID"] }). The extension URL format is chrome-extension://YOUR_EXTENSION_ID. Multiple extensions can be specified, or wildcard pattern chrome-extension://* can be used to trust all extensions (less secure, only for development).

Security warning about wildcard extension origins

Using wildcard pattern chrome-extension://* in trustedOrigins reduces security by trusting all browser extensions. It is safer to explicitly list each extension ID you trust. Only use wildcards for development and testing purposes.

Dynamic baseURL configuration with allowedHosts

Configure baseURL as an object with an allowedHosts allowlist to support multiple hostnames. When a request comes in, Better Auth extracts the host from x-forwarded-host, host header, or the request URL (in that order), validates it against allowedHosts, and uses the matched value to build the request-specific base URL. Wildcard patterns like *.vercel.app are supported.

baseURL allowedHosts option structure

The baseURL option accepts an object with the following properties: allowedHosts (array, required) - list of allowed hostnames and patterns; protocol (string, optional) - explicitly set 'http' or 'https'; fallback (string, optional) - URL to use if incoming host does not match allowedHosts.

Dynamic baseURL fallback behavior

By default, Better Auth throws an error if the incoming host does not match any entry in allowedHosts. Set the fallback option explicitly to handle unmatched hosts by falling back to a canonical domain. Use fallback only when it is clearly preferable to fail the request.

baseURL security model with allowlist

Dynamic base URL uses an allowlist model: only hosts listed in allowedHosts are accepted, x-forwarded-host and host headers are sanitized and validated before use, unknown hosts throw unless fallback is provided, and allowedHosts are automatically added to trustedOrigins (localhost entries get both http and https). This keeps multi-domain support explicit instead of trusting arbitrary headers or platform-specific behavior.

baseURL for Vercel deployment example

For Vercel deployments with preview URLs, configure baseURL with allowedHosts including your production domains and a wildcard pattern for preview URLs: allowedHosts: ["myapp.com", "www.myapp.com", "*.vercel.app"].

baseURL for development and production example

To support both development and production environments, configure baseURL with localhost entries for development and production domains, and conditionally set protocol based on NODE_ENV: baseURL: { allowedHosts: ["localhost:3000", "localhost:5173", "myapp.com", "*.vercel.app"], protocol: process.env.NODE_ENV === "development" ? "http" : "https" }.

baseURL for multiple production domains example

To support multiple production domains, configure baseURL with allowedHosts containing all domains and protocol set to 'https': baseURL: { allowedHosts: ["myapp.com", "myapp.co.uk", "myapp.eu"], protocol: "https" }.

crossSubDomainCookies with dynamic baseURL

To share cookies across subdomains while using dynamic baseURL, enable crossSubDomainCookies in the advanced configuration. Better Auth will derive the cookie domain from the resolved host unless you set the domain property explicitly in the crossSubDomainCookies configuration.

crossSubDomainCookies configuration with forced domain

To force a shared parent domain for cookies across subdomains, set the domain property in the crossSubDomainCookies configuration within advanced: advanced: { crossSubDomainCookies: { enabled: true, domain: ".example.com" } }.

Use cases for dynamic baseURL

Dynamic baseURL is useful when your app is served from multiple hostnames, such as custom domains (myapp.com and www.myapp.com), preview deployments (my-app-abc123.vercel.app), or branch environments (feature-branch.myapp.com).

1.7 upgrade command

To upgrade Better Auth from 1.6 to 1.7, run `npx auth@rc upgrade`. The `rc` tag is required because 1.7 is a release candidate; `@latest` still resolves to 1.6. After upgrading `better-auth`, also upgrade every `@better-auth/*` package to the `rc` version. Once 1.7 is stable, use `@latest` or drop the tag entirely.

Node.js version requirement for 1.7

The Better Auth CLI in 1.7 requires Node.js 22.12 or newer to run.

Schema migration before deploying 1.7

Run the schema migration before deploying 1.7 if any changed feature applies to your project. Use CLI migration commands: `npx auth@rc generate` followed by `npx auth@rc migrate`. If you manage your own schema with Drizzle or Prisma, run `generate` and apply the result through your own migration tooling.

1.7 features that change the schema

The following features add or change database tables in 1.7. Protected resources: new resource tables and key columns (no manual step). Resource-bound tokens: nullable resource columns on token tables (no manual step). DPoP: token-binding column (no manual step). Refresh-token reuse window: cached replay-response column on refresh tokens (no manual step). Authorization-code replay: indexed `authorizationCodeId` column on both token tables (no manual step). Back-channel logout: logout-URL and revoked columns (no manual step). Requested user-info claims: requested-claims column on token and consent tables (no manual step). Organization team counters: `team.memberCount` and `teamMember.membershipKey` columns (no manual step). SCIM org scoping: `organizationId` required, `userId` removed, `providerKey` added (requires manual step). SCIM groups: new `scimGroup`, `scimGroupMember`, `scimGroupRole`, and `scimGroupRoleGrant` tables (no manual step). Provider client store: `oauthApplication` becomes `oauthClient`, plus new token tables (requires manual step).

SCIM migration: reclaim, remap, then migrate

Before running `auth migrate` for SCIM, perform three steps. First, delete pre-1.7 `scimProvider` rows so connections re-register cleanly, or assign each row an `organizationId` and a unique `providerKey` by hand. Second, rewrite the `providerId` on SCIM-managed `account` rows to `scim:{organizationId}:{providerId}`, or `scim:{providerId}` for app-level static providers. Scope this rewrite to known SCIM rows only so unrelated accounts sharing a provider id are left untouched. Third, run `migrate`, then regenerate the connections' tokens. Skipping step 2 makes pre-1.7 SCIM users invisible to the provisioner: updates and deletes miss them, and re-provisioning creates duplicate users.

Migrating OAuth provider client data from oidcProvider or MCP

`@better-auth/oauth-provider` stores registered clients in `oauthClient`, not the old `oauthApplication`. The `oauthAccessToken.accessToken` column is renamed to `token`. The `auth migrate` command only adds tables and columns; it never renames or copies data. This leaves `oauthClient` empty and every registered client stranded in `oauthApplication`, while the `NOT NULL UNIQUE` `token` column aborts the `ALTER` on `oauthAccessToken`. The old and new client tables are not column-compatible: `redirectUrls` becomes `redirectUris` array, `metadata` becomes JSON, and new columns like `grantTypes` and `tokenEndpointAuthMethod` are added. Before cutover, copy clients across with field mapping or re-register them. Dynamically registered clients re-register themselves. The legacy `oauthAccessToken` rows are ephemeral, so drop or rename that table before running `migrate`.

OIDC SSO works on Cloudflare Workers in 1.7

OIDC SSO with discovery now works on Cloudflare Workers. A discovery or token endpoint that redirects is rejected with a clear configuration error. Point the config at the final URL if a provider endpoint redirects.

Multi-host `allowedHosts` no longer trusts forwarded headers by default

This affects only deployments that use a dynamic `baseURL` with `allowedHosts` to serve more than one host. A static `baseURL` string or no `baseURL` already ignored forwarded headers and is unchanged. For the `allowedHosts` path the default flipped: the auth origin now resolves from the `Host` header, and `x-forwarded-host` / `x-forwarded-proto` are ignored unless opted in. A multi-host deployment that relied on `x-forwarded-host` breaks after upgrade with a message like `Host "..." is not in the allowed hosts list`. If the proxy exposes the public hostname only through `x-forwarded-host`, set `advanced.trustedProxyHeaders: true`. Setups where the proxy rewrites the `Host` header for you (nginx, Vercel, Cloudflare, Netlify) need no change.

IdP redirects and DPoP need canonical origin

IdP redirects and native DPoP read the incoming request origin. Behind a custom server or TLS-terminating proxy, that origin can be the internal bind address rather than the public origin. The provider returns `consentPage` and `loginPage` as relative paths, so a server-side redirect needs them resolved against an absolute origin. Native DPoP compares the proof's `htu` against the URL the token endpoint computes. Treat `baseURL` as the server identity and canonicalize the incoming request scheme and host to it at the route boundary before the provider reads the request.

1.7 upgrade checklist order

When upgrading to 1.7, follow this order: First, apply data steps from "Before you upgrade" if they affect your project: the SCIM reclaim and `account.providerId` remap, and the `oidcProvider` or MCP client-data move. These run before `migrate`. Second, generate and run the migration with `npx auth@rc generate` then `npx auth@rc migrate`. Third, regenerate SCIM tokens for any connection you reclaimed.

Tracking 1.7 prereleases and reverting changes

If you upgraded straight from 1.6 to final 1.7, skip prerelease tracking. It applies only if you adopted a 1.7 beta and followed its changes. A breaking change in one beta can be reverted in a later beta. Read `revert` entries in the changelog, not just `feat` and `fix`; a revert is a migration for you. Bump only the packages that carry the change and confirm plugins do not import the reverted surface before rebuilding. A clear case is OAuth scopes: an early beta moved `account.scope` into `grantedScopes` array, and a later beta reverted it to the original `account.scope` string. If you adopted `grantedScopes` column, drop it and restore `scope` on every schema you changed.

Supabase migration invalidates all active sessions

Migrating from Supabase Auth to Better Auth will invalidate all active sessions. The migration guide does not currently cover migrating two-factor (2FA) or Row Level Security (RLS) configurations, though both should be possible with additional steps.

Back up database before Supabase migration

Create a full backup of both your Supabase database and target database before running any migration scripts, as the guide modifies production data.

Steps to migrate from Supabase Auth to Better Auth

The migration process involves: 1) Install Better Auth following the installation guide, 2) Connect to your database using DATABASE_URL and pg package, 3) Enable email and password authentication in auth config, 4) Setup social providers used in Supabase (missing any may cause user data loss), 5) Add plugins matching Supabase features (admin for is_super_admin/banned_until, anonymous for signInAnonymously, phoneNumber for phone number signups), 6) Add required additional user fields (userMetadata, appMetadata, invitedAt, lastSignInAt), 7) Run npx auth migrate to create tables, 8) Run migration script to migrate users and accounts from Supabase.

Required additional fields for Supabase migration

To minimize data loss when migrating from Supabase Auth, configure the following additional user fields: userMetadata (type: 'json', required: false, input: false), appMetadata (type: 'json', required: false, input: false), invitedAt (type: 'date', required: false, input: false), and lastSignInAt (type: 'date', required: false, input: false).

Migration script configuration options

The migration script supports three configuration options: batchSize (number of users to process in each batch; higher values = faster migration but more memory usage; default: 5000; recommended 5000-10000), resumeFromId (resume from a specific user ID using cursor-based pagination; useful for resuming interrupted migrations; default: null to start from beginning), and tempEmailDomain (temporary email domain for phone-only users who need an email for Better Auth; format: {phone_number}@{tempEmailDomain}; default: 'temp.better-auth.com').

Database connection setup for Supabase migration

To connect to your Supabase database during migration: 1) Install pg package with 'npm install pg', 2) Copy DATABASE_URL from Supabase project, 3) Pass it to betterAuth config using 'database: new Pool({ connectionString: process.env.DATABASE_URL })'.

Enable email and password authentication for Supabase migration

To enable email and password authentication in Better Auth for migration, add to auth config: emailAndPassword: { enabled: true }. Optionally add emailVerification config separately for email verification requirement.

Configure social providers for Supabase migration

When migrating from Supabase, add all social providers used in Supabase to auth config under socialProviders. Missing any providers may cause user data loss during migration. Example for GitHub: socialProviders: { github: { clientId: process.env.GITHUB_CLIENT_ID!, clientSecret: process.env.GITHUB_CLIENT_SECRET! } }

Supabase migration password hashing algorithm change

Since Supabase uses bcrypt for password hashing while Better Auth defaults to scrypt, configure Better Auth to use bcrypt for password verification during migration. Install bcrypt with 'npm install bcrypt' and '@types/bcrypt' as dev dependency, then add to emailAndPassword config: password: { hash: async (password) => await bcrypt.hash(password, 10), verify: async ({ hash, password }) => await bcrypt.compare(password, hash) }

Supabase to Better Auth API method mapping

When updating code from Supabase Auth to Better Auth, use these mappings: supabase.auth.signUp → authClient.signUp.email, supabase.auth.signInWithPassword → authClient.signIn.email, supabase.auth.signInWithOAuth → authClient.signIn.social, supabase.auth.signInAnonymously → authClient.signIn.anonymous, supabase.auth.signOut → authClient.signOut, supabase.auth.getSession → authClient.getSession (or authClient.useSession for reactive state).

Supabase migration memory requirements for large datasets

The migration script uses keyset pagination (cursor-based) which efficiently handles large datasets without loading everything into memory. For very large migrations (500k+ users), you may need to increase Node's memory limit with NODE_OPTIONS="--max-old-space-size=8192".

Environment variables for Supabase migration script

The migration script requires two environment variables: FROM_DATABASE_URL (Supabase database connection string; reads from auth.users schema) and TO_DATABASE_URL (target Postgres database connection string; writes to public.user schema). If migrating within the same Supabase Postgres database (from auth schema to public schema), both URLs can be the same DATABASE_URL.

Migration script batch processing and resume functionality

The migration script processes users in batches with cursor-based pagination. If a migration is interrupted, set resumeFromId to the last processed user ID to resume from that point without reprocessing earlier users. The script tracks the lastProcessedId and can be resumed multiple times.

SSO migration from Supabase requires plugin installation

To migrate Supabase Enterprise SSO (SAML) to Better Auth, install the SSO plugin with 'npm install @better-auth/sso' and add sso() to the plugins array in auth config.

Export Supabase SSO providers for migration

To export Supabase SSO providers for migration: 1) List all SSO providers using 'supabase sso list --project-ref <your-project-ref>' (note: does not include metadata XML), 2) Export each provider's full details including metadata XML using 'supabase sso show <provider-id> --project-ref <your-project-ref> -o json' and save to JSON file. The metadata XML field is required for migration.

SSO provider migration data structure

A migrated Better Auth SSO provider requires: id (generated), providerId (unique; format: sso-{original-supabase-id}), issuer (from Supabase entity_id), domain (comma-separated list of domains), oidcConfig (null), samlConfig (JSON stringified SAML config with issuer, entryPoint, cert, idpMetadata, spMetadata, identifierFormat, and mapping), organizationId (null or organization ID), createdAt and updatedAt (ISO date strings).

SSO attribute mapping in Supabase to Better Auth migration

When migrating SSO, transform Supabase attribute mapping to Better Auth format. Supabase supports { name: 'attr' } and { names: ['attr1', 'attr2'] }; Better Auth uses a single attribute name, so take the first match. Map these attributes: id (to nameID), email (from Supabase 'email' mapping or 'email' default), name (from Supabase 'name' mapping or 'displayName' default), firstName (from Supabase 'first_name' mapping or 'givenName' default), lastName (from Supabase 'last_name' mapping or 'surname' default).

SSO metadata configuration in Better Auth migration

Better Auth requires inline IdP metadata XML in samlConfig, not a URL. If Supabase provided only a metadata_url, fetch the XML from that URL before migration. The samlConfig should include: idpMetadata: { metadata: metadataXml } (required), spMetadata with entityID pointing to Better Auth endpoint, and identifierFormat (defaults to 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress' if not specified by Supabase).

Update Identity Provider settings after SSO migration

After migrating SSO from Supabase to Better Auth, update your IdP (Okta, Azure AD, Google Workspace, etc.) settings: 1) Change ACS URL / Reply URL from 'https://<project>.supabase.co/auth/v1/sso/saml/acs' to 'https://yourapp.com/api/auth/sso/saml2/sp/acs/<providerId>', 2) Change Entity ID / Audience from 'https://<project>.supabase.co/auth/v1/sso/saml/metadata' to 'https://yourapp.com/api/auth/sso/saml2/sp/metadata?providerId=<providerId>'. Replace <providerId> with the migrated provider ID (e.g., sso-550e8400-e29b-41d4-a716-446655440000).

Retrieve Better Auth SSO provider metadata

To retrieve the Service Provider (SP) metadata for a Better Auth SSO provider, make a GET request to: 'https://yourapp.com/api/auth/sso/saml2/sp/metadata?providerId=<providerId>'. This metadata can be shared with IdP administrators if needed.

SSO client API in Better Auth

To use SSO authentication in Better Auth client: 1) Import ssoClient from '@better-auth/sso/client', 2) Add to createAuthClient plugins: plugins: [ssoClient()], 3) Sign in using authClient.signIn.sso() with domain, email (domain extracted automatically), or providerId, plus callbackURL option for redirect after authentication.

SSO migration invalidates existing sessions

SSO migration from Supabase to Better Auth requires updating Identity Provider configuration with new callback URLs and will invalidate existing SSO sessions. Plan for a brief cutover window during the migration.

Test SSO flow after migration

Before going live after SSO migration, test: 1) SP-initiated SSO (start from app's login page, enter SSO-enabled email domain), 2) Verify user attributes (check name, email, other attributes are mapped correctly), 3) Test existing users (ensure users who previously logged in via SSO can still access accounts), 4) Test new users (verify new SSO users are created correctly).

SSO SAML signature validation errors during migration

If you encounter SAML signature validation errors after SSO migration, ensure your IdP's certificate is current. Some IdPs rotate certificates periodically—check the metadata URL for the latest certificate.

SSO attribute mapping issues during migration

If user attributes aren't populating correctly after SSO migration, inspect the SAML assertion from your IdP using browser developer tools. Update the mapping field in samlConfig to match the exact attribute names your IdP sends.

Handle multiple domains in SSO migration

If you have multiple domains for a single SSO provider, separate them with commas in the domain field. Example: 'company.com,company.org'.

Migration script validates Better Auth configuration before running

The migration script includes validateAuthConfig() function that checks: 1) emailAndPassword.enabled must be true (blocking error if false), 2) Optional plugins (admin, anonymous, phone-number) - warns if missing as related Supabase data will be skipped, 3) Required additional fields (userMetadata, appMetadata, invitedAt, lastSignInAt) - blocking error if missing or misconfigured. Configuration errors must be fixed before migration can proceed.

Migration script user skip conditions

During Supabase migration, users are skipped (not migrated) if: 1) User has no email and no phone number, 2) User has no email and phoneNumber plugin is not enabled, 3) User is marked as deleted (deleted_at is set), 4) User has banned_until set and admin plugin is not enabled. These users are counted in the skip count and not migrated to Better Auth.

Retrieve user name from Supabase metadata during migration

During user migration from Supabase, the script attempts to retrieve the user's name from multiple sources in this priority order: 1) raw_user_meta_data.name, 2) raw_user_meta_data.full_name, 3) raw_user_meta_data.username, 4) raw_user_meta_data.user_name, 5) First identity's identity_data.name, 6) First identity's identity_data.full_name, 7) First identity's identity_data.username, 8) First identity's identity_data.preferred_username, 9) Email local part (before @), 10) Phone number, 11) 'Unknown' default.

Retrieve user image from Supabase metadata during migration

During user migration from Supabase, the script attempts to retrieve the user's image from multiple sources in this priority order: 1) raw_user_meta_data.avatar_url, 2) raw_user_meta_data.picture, 3) First identity's identity_data.avatar_url, 4) First identity's identity_data.picture. If none found, image is undefined (not set).

Migration script processes Supabase identities

During migration, for each user's identity in Supabase: 1) If provider is 'email', create account with providerId 'credential' and store encrypted_password, 2) If provider is in supportedProviders (social providers configured in Better Auth), create account with that provider name and store identity_data.sub or provider_id as accountId, 3) If provider starts with 'sso:', create account with providerId formatted as 'sso-{supabaseProviderId}' to match migrated SSO provider.

Migration script batch processing with PostgreSQL

The migration script uses batching to handle large datasets: 1) Each batch retrieves up to batchSize users using keyset pagination (WHERE u.id > lastId), 2) All users in batch are processed together in a single transaction, 3) User inserts use chunking to avoid exceeding PostgreSQL's 65000 parameter limit per query, 4) Account inserts similarly chunked to stay within parameter limits.

Migration script transaction handling and rollback

The migration script uses database transactions for reliability: 1) BEGIN transaction at start of each batch, 2) If any error occurs during batch processing, ROLLBACK entire batch, 3) Failed batch is counted as failureCount equal to number of users in that batch, 4) Error is logged and migration continues with next batch.

Migration script progress tracking and ETA calculation

The migration script tracks progress by: 1) Calculating percentage as (processedUsers / totalUsers) * 100, 2) Tracking speed as users/sec for each batch, 3) Calculating ETA based on elapsed time, average time per user, and remaining users, 4) Displaying ETA in human-readable format (hours, minutes, seconds), 5) Returning null ETA if no time has elapsed yet or no users processed.

Give your agent this brain