Authentication and email
Configure Better Auth, Google OAuth, verification mail, password reset, and contact delivery.
VibeAny uses Better Auth backed by PostgreSQL. It supports email and password with required email verification, optional Google OAuth, and optional Google One Tap. Authentication email and contact-form delivery share one Resend-or-SMTP adapter.
The active authentication implementation is not Supabase Auth. Do not add Supabase public auth variables to this setup.
Authentication routes
Route groups do not appear in public URLs. For the English locale, the canonical pages are:
| Purpose | Route |
|---|---|
| Sign in | /en/login |
| Sign up | /en/signup |
| Request a password reset | /en/forgot-password |
| Set a new password | /en/reset-password |
| Verify an email address | /en/verify-email |
| Better Auth API | /api/auth/* |
The same page paths work under /zh and /es. /dashboard and /account are protected; an anonymous request is redirected to the localized login page with a safe return path.
Email and password
Start the local database and Mailpit:
docker compose up -d db mailpitGenerate a signing secret:
openssl rand -base64 32Configure .env.local:
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/vibeany
BETTER_AUTH_SECRET=<generated-secret>
NEXT_PUBLIC_ENABLE_EMAIL_AUTH=true
NEXT_PUBLIC_SITE_URL=http://localhost:3000
NEXT_PUBLIC_APP_URL=http://localhost:3000Apply migrations and start the app:
npm run db:migrate
npm run devEmail/password behavior includes:
- password hashes generated with bcrypt cost 12
- email verification required before sign-in
- password-reset delivery through the configured email adapter
- account linking for trusted Google accounts
- user self-deletion support
When NEXT_PUBLIC_ENABLE_EMAIL_AUTH is false, signup, forgot-password, reset-password, and verify-email pages redirect to login. The login page can still offer Google when Google OAuth is configured.
Auth base URL
Leave BETTER_AUTH_URL unset locally so Better Auth follows the actual localhost origin. In production, use the canonical HTTPS origin:
BETTER_AUTH_URL=https://your-domain.com
NEXT_PUBLIC_SITE_URL=https://your-domain.com
NEXT_PUBLIC_APP_URL=https://your-domain.comServer-side base URL resolution prefers BETTER_AUTH_URL, then NEXT_PUBLIC_APP_URL, then NEXT_PUBLIC_SITE_URL in production. Client auth calls are same-origin so preview deployments do not call a different build-time domain.
Google OAuth
Create a Google OAuth web client and register this local callback:
http://localhost:3000/api/auth/callback/googleRegister the equivalent HTTPS callback for every production origin you support. Configure:
GOOGLE_CLIENT_ID=<google-web-client-id>
GOOGLE_CLIENT_SECRET=<google-web-client-secret>
NEXT_PUBLIC_GOOGLE_CLIENT_ID=<same-google-web-client-id>
NEXT_PUBLIC_ENABLE_ONE_TAP=falseThe server client ID and secret enable the Google login method. The public client ID is also required for One Tap; set NEXT_PUBLIC_ENABLE_ONE_TAP=true only after ordinary Google OAuth works on the same origin.
Production refuses to start when neither a complete Google configuration nor email/password authentication is enabled.
Optional signup Turnstile
To protect registration with Cloudflare Turnstile, configure both keys:
NEXT_PUBLIC_TURNSTILE_SITE_KEY=<public-site-key>
TURNSTILE_SECRET_KEY=<server-secret-key>Do not configure only one side. The server verifies the token before Better Auth accepts the signup request.
Local email with Mailpit
When RESEND_API_KEY is absent, the email adapter uses SMTP. Its local defaults match Docker Compose:
- host:
localhost - port:
1025 - web inbox: http://localhost:8025
- default sender:
onboarding@resend.dev
No public email is sent. Use the Mailpit inbox to open verification and password-reset links during development.
If you use an external database, remember that the setup wizard does not start Mailpit for that choice. Start it explicitly with docker compose up -d mailpit.
Production email with Resend
Use a sender on a domain verified in your Resend account:
RESEND_API_KEY=<resend-api-key>
RESEND_FROM_EMAIL=auth@your-domain.com
ADMIN_EMAILS=admin@your-domain.comWhen the Resend key is present, the adapter sends through the Resend HTTP API. ADMIN_EMAILS has a security-sensitive dual purpose: it is both the administrator allowlist used by dashboard and admin API authorization and the recipient list for contact-form notifications. Include only controlled accounts that should be able to manage users and adjust credits. Do not add a support alias, team distribution list, or third-party inbox merely to receive contact mail.
If ADMIN_EMAILS is empty, contact notifications fall back to brand.emails.support from src/lib/config/brand.ts, and no email account is granted admin access. If ordinary support recipients must differ from administrators, split the configuration in your application before launch rather than broadening ADMIN_EMAILS.
The Resend development sender may be useful for a restricted provider test, but production should use your verified domain and intended recipients.
Production email with SMTP
To use SMTP instead of Resend, omit RESEND_API_KEY and configure the complete SMTP set:
SMTP_HOST=<smtp-host>
SMTP_PORT=<smtp-port>
SMTP_USER=<smtp-user>
SMTP_PASS=<smtp-password>
RESEND_FROM_EMAIL=auth@your-domain.comThe sender variable retains the name RESEND_FROM_EMAIL for both transports. Production preflight requires the complete host, port, user, password, and sender configuration when any SMTP value is present.
Email templates and locale behavior
Authentication verification and reset templates are defined in src/lib/auth.ts. Contact notification formatting lives in the application email use case, while transport selection lives in src/infrastructure/email/resend-smtp-email-adapter.ts.
English, Chinese, and Spanish authentication message sets exist, but the current verification and reset callbacks explicitly select English. If localized auth mail is a product requirement, store or derive the user's preferred locale and pass it into that selection before launch.
Acceptance checklist
Verify the complete local journey:
- Open
/en/signupand register a dedicated test account. - Confirm that unverified credentials cannot sign in.
- Open the verification message in Mailpit and follow its local-origin link.
- Sign in and load
/en/dashboardand/en/account. - Sign out and confirm the protected page redirects to login.
- Request a reset at
/en/forgot-password. - Open the reset message, set a new password, and sign in again.
For production, repeat the same journey with a dedicated test inbox and the real HTTPS origin. Confirm that verification and reset links never point to localhost or another environment.
Finally run npm run preflight. With email auth enabled, preflight fails unless it finds a complete Resend or SMTP delivery configuration.
Review Environment configuration for all related variables and Database for the required auth tables and migration workflow.