Authentication

Your project ships with a full authentication system built on Better Auth. It supports email/password sign-in, Google OAuth, email verification, password reset, and automatic organization setup — all wired up and ready to use.

Authentication methods

Email and password

Email/password is always enabled. Passwords require a minimum of 8 characters.

When AUTH_BYPASS_EMAIL_VERIFICATION is not set, new users must verify their email before accessing the app. See email verification for details.

Google OAuth

Google OAuth is enabled automatically when both AUTH_GOOGLE_ID and AUTH_GOOGLE_SECRET are set. If either is missing, the Google button won't appear on the sign-in page.

# packages/backend/.env
AUTH_GOOGLE_ID=your-google-client-id
AUTH_GOOGLE_SECRET=your-google-client-secret

To get your credentials:

  1. Go to the Google Cloud Console.
  2. Create an OAuth 2.0 Client ID (Web application).
  3. Add http://localhost:3011/api/auth/callback/google as an authorized redirect URI.
  4. Copy the client ID and secret to your .env file.

Tip: For production, add your production backend URL as an additional redirect URI (e.g., https://api.yourdomain.com/api/auth/callback/google).

Sign-up flow

  1. User fills in email and password on /auth/sign-up.
  2. If email verification is required, user is redirected to /auth/verify-email/request with a prompt to check their inbox.
  3. User clicks the verification link in the email → auto-signed in.
  4. authGuardFrontend checks organization membership:
    • Single org mode — user is auto-joined to the default organization (see organization setup).
    • Multi org mode — user is redirected to /auth/organization to create or select an organization.
  5. User completes profile onboarding (first name, last name) at /auth/profile-onboard.
  6. User lands on the home page.

If the user was invited, the sign-up URL includes ?invitationToken=&email= — the email field is pre-filled and locked, and the invitation is automatically accepted after sign-up.

Sign-in flow

  1. User enters credentials on /auth/sign-in (or clicks "Sign in with Google").
  2. Session is created with a cookie.
  3. If the user has exactly one organization membership, the active organization and member are auto-set on the session.
  4. If the user has multiple memberships (multi org mode), they're redirected to /auth/organization to choose.
  5. User is redirected to the home page (or the original ?redirect path).

Email verification

When email verification is enabled (the default), new users receive an email with a verification link after sign-up. The link points to /auth/verify-email/confirm?token=..., which auto-signs the user in on success.

The /auth/verify-email/request page shows a "check your email" message with a resend button in case the original email didn't arrive.

If a user tries to sign in without verifying their email, they'll see an error and be redirected to the verification request page.

Bypassing for development

Set AUTH_BYPASS_EMAIL_VERIFICATION=true in your backend .env to skip email verification entirely. Users are signed in immediately after sign-up without needing to verify. This is useful during development when you don't have email configured.

Password reset

  1. User clicks "Forgot password?" on the sign-in page → navigates to /auth/password-reset/request.
  2. User enters their email → a reset link is sent.
  3. User clicks the link → /auth/password-reset/confirm?token=....
  4. User enters a new password (min 8 characters) → redirected to sign-in.

Session management

Sessions are cookie-based with the prefix project (e.g., project.session_token). The session stores the activeOrganizationId and activeMemberId so the backend knows which organization context to use for each request.

When Redis is configured (REDIS_URL), sessions and auth lookups are cached for faster performance. Without Redis, everything falls back to the database.

Organization switching

In multi org mode, users can switch organizations via the organization selector. This calls the set-active-organization endpoint, which updates the session's activeOrganizationId and activeMemberId.

Organization setup after sign-in

How organization membership is handled depends on ORGANIZATION_MODE:

Single org mode

A default organization is created automatically on first sign-up. All subsequent users are auto-joined with the role defined by ORGANIZATION_DEFAULT_ROLE (defaults to the first role). No organization selection page is shown.

If a pending invitation exists for the user's email, it's accepted automatically instead of using the default role.

Multi org mode

Users are redirected to /auth/organization where they can:

  • Select an existing organization they belong to.
  • Accept a pending invitation to join an organization.
  • Create a new organization.

If the user has exactly one membership and no pending invitations, the organization is auto-selected.

Multi-domain mode

Similar to multi org mode, but the organization is resolved from the domain. The frontend fetches the organization matching the current hostname. If the user isn't a member of that organization, they're shown the no-permissions page.

Profile onboarding

After organization setup, if the user hasn't completed their profile, they're redirected to /auth/profile-onboard. This page collects:

  • First name (required)
  • Last name (required)
  • Avatar (optional)
  • Notification preferences (toggle)

The user can't access the app until this step is completed.

API key authentication

Your project supports API key authentication for programmatic access. API keys are sent via the x-api-key header.

Key features:

  • Organization-scoped — each key is tied to a specific organization via metadata.
  • Optional permission scoping — keys can be restricted to specific actions (e.g., read-only for a resource).
  • Rate limiting — 1,000 requests per 24 hours per key.
  • Expiration — keys can be set to expire between 1 and 365 days. No expiration by default.
  • Redis caching — when Redis is configured, key lookups are cached for performance.

See authorization for details on how API key permissions are enforced.

MCP authentication

Your project includes an MCP (Model Context Protocol) server that lets AI assistants like Claude interact with your data. MCP authentication uses OAuth 2.0 with JWT tokens.

The OAuth discovery endpoints are auto-registered at /.well-known/oauth-authorization-server and /.well-known/oauth-protected-resource. When a client connects, it's redirected to the sign-in page for user authorization, then receives a JWT token for subsequent requests.

MCP requests skip the remote session validation step (since they use JWT), but all other authorization layers apply.

Environment variables

VariableRequiredDefaultDescription
AUTH_SECRETYesSecret for session encryption
AUTH_BYPASS_EMAIL_VERIFICATIONNofalseSet to "true" to skip email verification
AUTH_GOOGLE_IDNoGoogle OAuth client ID
AUTH_GOOGLE_SECRETNoGoogle OAuth client secret
ORGANIZATION_MODENomultisingle, multi, or multi-domain
ORGANIZATION_DEFAULT_ROLENoDefault role for new members in single org mode
ORGANIZATION_MULTI_DOMAIN_MODENosubdomainsubdomain or domain (for multi-domain mode)
ORGANIZATION_DOMAIN_TRUSTED_ORIGINSNoComma-separated trusted origins for domain mode
REDIS_URLNoRedis URL for session and auth caching
RECAPTCHA_SITE_KEYNoGoogle reCAPTCHA v3 site key
RECAPTCHA_SECRET_KEYNoGoogle reCAPTCHA v3 secret key
EMAIL_FROMNoSender email address
EMAIL_SMTP_HOSTNoSMTP host
EMAIL_SMTP_PORTNoSMTP port
EMAIL_SMTP_USERNoSMTP username
EMAIL_SMTP_PASSWORDNoSMTP password

Frontend auth pages reference

RoutePurpose
/auth/sign-inEmail/password and Google OAuth sign-in
/auth/sign-upRegistration form (supports ?invitationToken and ?email)
/auth/sign-outSign out
/auth/callbackOAuth callback handler
/auth/verify-email/request"Check your email" page with resend button
/auth/verify-email/confirmToken-based email verification
/auth/password-reset/requestEnter email for password reset
/auth/password-reset/confirmSet new password with reset token
/auth/email-change/confirmConfirm email address change
/auth/organizationCreate or select an organization (multi org mode)
/auth/profile-onboardFirst/last name and avatar onboarding
/auth/no-permissionsShown when member is disabled or has no org access
/auth/profileEdit profile (authenticated)
/auth/password-changeChange password (authenticated)
/auth/email-changeRequest email change (authenticated)
/auth/invitationAccept an invitation link