Better Auth
Better Auth is the repo’s web-user auth authority. Payload CMS users are separate native CMS operator accounts.
What It Owns
Section titled “What It Owns”apps/authowns Better Auth routes, sessions, workforce SSO provider registration, sign-in policy, and web-user projection setup.packages/nuxt-auth-layerowns the shared Nuxt auth client, sign-in pages, session store, middleware, onboarding pages, and auth BFF routes used by Nuxt web-user apps.apps/webuses that layer for the authenticated Nuxt workspace.apps/web-token-replaceruses that layer for the authenticated TODO shell and future token replacer product.apps/apivalidates Better Auth sessions before Mastra-backed runtime operations.- Connector OAuth account binding remains separate from workforce SSO, but both are tied back to Better Auth web users.
- Connector OAuth account deletion is a fresh-session action: the web BFF rejects stale sessions before connector cleanup or Better Auth unlinking, and the browser prompt reauthenticates with the currently allowed web sign-in method.
- Full name and avatar image are Better Auth web-user profile fields. Workforce SSO refreshes them from trusted OIDC
nameandpictureclaims on login, while email/password avatars use auth-owned first-party upload/storage. - Better Auth 1.6.x rejects SSO provider ids that collide with configured social/trusted provider ids, but the repo still keeps its own SSO/social namespace validation so deployment policy fails closed before registration or callback handling.
- Workforce SSO uses Better Auth SSO
domainVerificationfor callback trust and same-email account linking. Deployment-managed registration marks configured provider rowsdomainVerified: true, including existing rows reconciled after an upgrade. Better Auth HTTP SSO provider-management routes are disabled for browser/API callers; setup uses internal Better Auth APIs for configured provider reconciliation. Configured provider drift is reconciled through Better Auth’s in-place update API, not provider deletion, so Better Auth 1.6.21+ delete-provider account cleanup cannot remove linked workforce SSO account rows during setup. Once linked SSO accounts exist, setup refuses issuer, trusted-domain, client-id, OIDC endpoint, and subject-mapping drift instead of mutating that provider identity boundary in place. SSO provider ids must not be placed inaccountLinking.trustedProviders, and the deprecated Better Auth SSOtrustEmailVerifiedoption is not used. - Better Auth direct packages are currently on 1.6.23. The 1.6.20 through 1.6.23 patch audit adds upstream hardening for account-linking diagnostics, cookie max-age handling, OAuth profile/state validation, OAuth server-side redirect refusal, SSO domain/audience validation, API-key IP/rate-limit handling, and Mongo adapter counters. These are dependency-level fixes; they do not change the repo’s workforce SSO, connector OAuth, API-key, or Mongo ownership model.
- The root package override pins transitive
better-authconsumers to the same canonical patch version so packages such as@mastra/auth-better-authdo not retain an older nested Better Auth runtime. - Better Auth 1.6.21 centralizes API-key rate-limit IP resolution and hardens multi-hop
X-Forwarded-Forhandling. Do not addadvanced.ipAddress.trustedProxiesoripAddressHeadersby default; add them only for an accepted deployment topology with known proxy IPs/CIDRs or sanitized headers. - Shared Nuxt sign-in owns post-auth redirects and consumes successful email/password token/user payloads directly. Better Auth intentionally delays its client session-atom refetch after sign-in, so the UI must not depend on an immediate refetch before redirecting.
Architecture Decision
Section titled “Architecture Decision”Decision: use Better Auth for web-user identity only, and do not reuse it for Payload CMS operator accounts.
This keeps the customer CMS, Leverage CMS, and web-user account domains separate. It also lets the API and web apps share a single web-session model while preserving native Payload admin auth for the two CMS control planes.
AUTH_UI_BASE_URL remains the primary web UI origin for auth-owned email links and feedback flows. Additional Nuxt web-user origins, including local web-token-replacer on http://localhost:3006, are configured with AUTH_WEB_ADDITIONAL_UI_ORIGINS and map to the same web sign-in policy.
How To Upgrade
Section titled “How To Upgrade”-
Read the GitHub release notes and each linked package changelog first, especially
better-auth, MongoDB adapter, SSO, API-key, generic OAuth, and session changes. -
Upgrade the Better Auth packages in the owning workspaces together:
Terminal window # from apps/authbun update better-auth @better-auth/mongo-adapter @better-auth/sso# from apps/webbun update better-auth @better-auth/sso# from apps/web-token-replacerbun update better-auth @better-auth/sso# from packages/authbun update better-auth @better-auth/api-key# from packages/nuxt-auth-layerbun update better-auth @better-auth/sso# from apps/apibun update better-auth -
If the changelog says to use the Better Auth CLI, use the current standalone CLI:
Terminal window bunx auth upgrade -
For MongoDB adapter deployments, do not expect Better Auth schema generation or migration to create Mongo schema. The official MongoDB adapter docs say MongoDB does not need generated schema migrations.
-
Run auth setup after SSO/plugin upgrades so configured workforce provider rows are reconciled for the current auth database. Local development can use the rerunnable bootstrap, which starts local Docker when safe before running setup:
Terminal window bun run initTo run only auth setup, use:
Terminal window bun run --cwd apps/auth setupThis is the supported deployment setup path that marks configured SSO providers
domainVerified: trueand updates configured provider drift in place. If Google or Microsoft web SSO returnsNo provider found for the issuerorProvider domain has not been verified, setup/release has not reconciled the configured provider row for the auth database used by the running service. If same-email SSO returnsaccount not linked, confirm the existing local credential user hasemailVerified: trueand the verified SSO provider domain matches the user’s email domain; in local Google testing, setGOOGLE_SSO_ALLOWED_DOMAINSorAUTH_SSO_DOMAINto the test account’s email domain instead of relying onlocalhost. Do not replace this path with provider delete/re-register; Better Auth 1.6.21+ provider deletion also removes linked account rows for that provider id. If setup refuses provider identity/trust drift because linked SSO accounts already exist, use a new provider id or intentionally clear pre-release linked-account data before rerunning setup. -
Run
bun run test:auth,bun run --cwd packages/nuxt-auth-layer test,bun run --cwd apps/api check-types, and the web/token-replacer auth or E2E lanes relevant to the change.
Current Limits
Section titled “Current Limits”- Do not inspect or mutate Better Auth Mongo records directly unless the task is an approved migration or repair.
- If raw Mongo access is unavoidable, verify the installed Better Auth adapter/source or an integration test for the exact stored shape first.
- Better Auth MongoDB experimental joins can improve related-data endpoints, but enabling them is a runtime behavior change and belongs in the auth specs and tests.
- Better Auth 1.6.20 through 1.6.23 does not require a Mongo schema migration for this repo’s active plugin set. Two-factor schema changes in that patch range do not apply unless the two-factor plugin is explicitly added in a future auth slice.
- Better Auth 1.6.23 adds a Yandex social provider, but the repo should not add it unless the connector/provider registry accepts Yandex as a product capability.
- SAML hardening in Better Auth SSO 1.6.21 is adopted as dependency defense in depth. Do not configure SAML providers without a separate accepted workforce SSO design and tests.
- Do not add arbitrary user-entered avatar/image URL fields. User-provided profile media must go through auth-owned upload, a 1 MB input cap, trusted browser-origin checks, server-side type validation, metadata-stripping re-encoding, and owner-only access control. First-party avatar uploads store the auth-owned managed path in Better Auth
user.image; external avatar URLs are acceptable only from trusted configured identity/provider claims. - If web email/password auth is disabled, connector OAuth deletion re-verification must not ask for a local password. SSO-created sessions use the configured workforce SSO provider; sessions whose method no longer matches policy require sign-out/sign-in before deletion.
- Do not re-enable Better Auth’s client auto-redirect plugin for the shared Nuxt auth client unless the app-owned sign-in sequencing is redesigned and covered by browser-level sign-in regression tests.
- Connector OAuth remains on Better Auth
genericOAuthuntil a connector-wide audit proves built-in social providers have parity for custom scopes, offline refresh tokens, account chooser prompts, redirect/local bridge behavior, typed profile mapping, connector registry ownership, and SSO/social namespace isolation.
Canonical Sources
Section titled “Canonical Sources”apps/auth/src/apps/auth/package.jsonpackages/nuxt-auth-layerapps/web/package.jsonapps/web-token-replacer/package.jsonapps/api/package.jsondocs/features/authentication/spec.mddocs/features/token-replacer/spec.mddocs/features/cms-control-plane-split/spec.md