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-secretTo get your credentials:
- Go to the Google Cloud Console.
- Create an OAuth 2.0 Client ID (Web application).
- Add
http://localhost:3011/api/auth/callback/googleas an authorized redirect URI. - Copy the client ID and secret to your
.envfile.
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
- User fills in email and password on
/auth/sign-up. - If email verification is required, user is redirected to
/auth/verify-email/requestwith a prompt to check their inbox. - User clicks the verification link in the email → auto-signed in.
authGuardFrontendchecks organization membership:- Single org mode — user is auto-joined to the default organization (see organization setup).
- Multi org mode — user is redirected to
/auth/organizationto create or select an organization.
- User completes profile onboarding (first name, last name) at
/auth/profile-onboard. - 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
- User enters credentials on
/auth/sign-in(or clicks "Sign in with Google"). - Session is created with a cookie.
- If the user has exactly one organization membership, the active organization and member are auto-set on the session.
- If the user has multiple memberships (multi org mode), they're redirected to
/auth/organizationto choose. - User is redirected to the home page (or the original
?redirectpath).
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
- User clicks "Forgot password?" on the sign-in page → navigates to
/auth/password-reset/request. - User enters their email → a reset link is sent.
- User clicks the link →
/auth/password-reset/confirm?token=.... - 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
| Variable | Required | Default | Description |
|---|---|---|---|
AUTH_SECRET | Yes | — | Secret for session encryption |
AUTH_BYPASS_EMAIL_VERIFICATION | No | false | Set to "true" to skip email verification |
AUTH_GOOGLE_ID | No | — | Google OAuth client ID |
AUTH_GOOGLE_SECRET | No | — | Google OAuth client secret |
ORGANIZATION_MODE | No | multi | single, multi, or multi-domain |
ORGANIZATION_DEFAULT_ROLE | No | — | Default role for new members in single org mode |
ORGANIZATION_MULTI_DOMAIN_MODE | No | subdomain | subdomain or domain (for multi-domain mode) |
ORGANIZATION_DOMAIN_TRUSTED_ORIGINS | No | — | Comma-separated trusted origins for domain mode |
REDIS_URL | No | — | Redis URL for session and auth caching |
RECAPTCHA_SITE_KEY | No | — | Google reCAPTCHA v3 site key |
RECAPTCHA_SECRET_KEY | No | — | Google reCAPTCHA v3 secret key |
EMAIL_FROM | No | — | Sender email address |
EMAIL_SMTP_HOST | No | — | SMTP host |
EMAIL_SMTP_PORT | No | — | SMTP port |
EMAIL_SMTP_USER | No | — | SMTP username |
EMAIL_SMTP_PASSWORD | No | — | SMTP password |
Frontend auth pages reference
| Route | Purpose |
|---|---|
/auth/sign-in | Email/password and Google OAuth sign-in |
/auth/sign-up | Registration form (supports ?invitationToken and ?email) |
/auth/sign-out | Sign out |
/auth/callback | OAuth callback handler |
/auth/verify-email/request | "Check your email" page with resend button |
/auth/verify-email/confirm | Token-based email verification |
/auth/password-reset/request | Enter email for password reset |
/auth/password-reset/confirm | Set new password with reset token |
/auth/email-change/confirm | Confirm email address change |
/auth/organization | Create or select an organization (multi org mode) |
/auth/profile-onboard | First/last name and avatar onboarding |
/auth/no-permissions | Shown when member is disabled or has no org access |
/auth/profile | Edit profile (authenticated) |
/auth/password-change | Change password (authenticated) |
/auth/email-change | Request email change (authenticated) |
/auth/invitation | Accept an invitation link |