Skip to content

Better Auth

Better Auth is the repo’s web-user auth authority. Payload CMS users are separate native CMS operator accounts.

  • apps/auth owns Better Auth routes, sessions, workforce SSO provider registration, sign-in policy, and web-user projection setup.
  • packages/nuxt-auth-layer owns the shared Nuxt auth client, sign-in pages, session store, middleware, onboarding pages, and auth BFF routes used by Nuxt web-user apps.
  • apps/web uses that layer for the authenticated Nuxt workspace.
  • apps/web-token-replacer uses that layer for the authenticated TODO shell and future token replacer product.
  • apps/api validates 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 name and picture claims 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 domainVerification for callback trust and same-email account linking. Deployment-managed registration marks configured provider rows domainVerified: 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 in accountLinking.trustedProviders, and the deprecated Better Auth SSO trustEmailVerified option 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-auth consumers to the same canonical patch version so packages such as @mastra/auth-better-auth do 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-For handling. Do not add advanced.ipAddress.trustedProxies or ipAddressHeaders by 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.

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.

  1. Read the GitHub release notes and each linked package changelog first, especially better-auth, MongoDB adapter, SSO, API-key, generic OAuth, and session changes.

  2. Upgrade the Better Auth packages in the owning workspaces together:

    Terminal window
    # from apps/auth
    bun update better-auth @better-auth/mongo-adapter @better-auth/sso
    # from apps/web
    bun update better-auth @better-auth/sso
    # from apps/web-token-replacer
    bun update better-auth @better-auth/sso
    # from packages/auth
    bun update better-auth @better-auth/api-key
    # from packages/nuxt-auth-layer
    bun update better-auth @better-auth/sso
    # from apps/api
    bun update better-auth
  3. If the changelog says to use the Better Auth CLI, use the current standalone CLI:

    Terminal window
    bunx auth upgrade
  4. 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.

  5. 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 init

    To run only auth setup, use:

    Terminal window
    bun run --cwd apps/auth setup

    This is the supported deployment setup path that marks configured SSO providers domainVerified: true and updates configured provider drift in place. If Google or Microsoft web SSO returns No provider found for the issuer or Provider 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 returns account not linked, confirm the existing local credential user has emailVerified: true and the verified SSO provider domain matches the user’s email domain; in local Google testing, set GOOGLE_SSO_ALLOWED_DOMAINS or AUTH_SSO_DOMAIN to the test account’s email domain instead of relying on localhost. 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.

  6. 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.

  • 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 genericOAuth until 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.
  • apps/auth/src/
  • apps/auth/package.json
  • packages/nuxt-auth-layer
  • apps/web/package.json
  • apps/web-token-replacer/package.json
  • apps/api/package.json
  • docs/features/authentication/spec.md
  • docs/features/token-replacer/spec.md
  • docs/features/cms-control-plane-split/spec.md