Authentication Configuration
Configure authentication methods, OAuth providers, email verification, and customize behavior for your application.
Vibestacks authentication is highly configurable. Enable or disable sign-in methods, configure OAuth providers, and customize behavior through configuration files.
Configuration File
Authentication settings are defined in the auth configuration:
export const auth = betterAuth({
// Database connection
database: drizzleAdapter(db, {
provider: "pg",
schema,
}),
// Email/password authentication
emailAndPassword: {
enabled: true,
requireEmailVerification: true,
},
// Social providers
socialProviders: {
google: {
clientId: env.GOOGLE_CLIENT_ID,
clientSecret: env.GOOGLE_CLIENT_SECRET,
},
// Add more providers here
},
// Plugins
plugins: [
magicLink({
sendMagicLink: async ({ email, url }) => {
// Email sending logic
},
}),
// stripe(), twoFactor(), etc.
],
})Security Configuration
Vibestacks includes several security features that should be configured before deploying to production.
Trusted Origins
Trusted origins prevent CSRF attacks and open redirects by only allowing authentication requests from approved domains:
// Your site URL is automatically trusted
// In development, localhost:3000 is also trustedconst trustedOrigins: string[] = [siteConfig.url];
if (process.env.NODE_ENV === "development") {
trustedOrigins.push("http://localhost:3000");
}
export const auth = betterAuth({
trustedOrigins,
// ...
})Production Requirement
Always ensure your production NEXT_PUBLIC_SITE_URL is correctly set. Requests from untrusted origins will be rejected.
Rate Limiting
Rate limiting protects against brute force attacks and abuse. Configure in config/app.ts:
authConfig = {
rateLimit: {
enabled: true, // Set to false to disable (not recommended)
window: 60, // Time window in seconds
max: 100, // Max requests per window
customRules: {
"/sign-up/email": { window: 60, max: 5 }, // 5 signups per minute
"/forgot-password": { window: 60, max: 3 }, // 3 reset requests per minute
"/magic-link/verify": { window: 10, max: 5 }, // 5 verifications per 10 seconds
},
},
}Rate limit data is stored in the database, so limits persist across server restarts and work in serverless environments.
Session Configuration
Control session duration and caching behavior:
authConfig = {
session: {
expiresIn: 60 * 60 * 24 * 7, // 7 days - session lifetime
updateAge: 60 * 60 * 24, // 1 day - refresh expiry after this much activity
cookieCache: {
enabled: true, // Cache session in cookie to reduce DB calls
maxAge: 5 * 60, // 5 minutes - revalidate after this period
},
},
}| Setting | Default | Description |
|---|---|---|
expiresIn | 7 days | How long until session expires completely |
updateAge | 1 day | Session expiry refreshes after this much activity |
cookieCache.enabled | true | Reduces database calls by ~80% |
cookieCache.maxAge | 5 min | How often to revalidate session from database |
Cookie Caching
With cookie caching enabled, session validation reads from the cookie instead of the database for most requests. The session is only verified against the database every 5 minutes, significantly improving performance.
Account Linking
Account linking allows users to connect multiple authentication methods (e.g., sign up with email, later add Google):
authConfig = {
accountLinking: {
enabled: true, // Allow linking multiple auth methods
},
}When enabled:
- Users who sign up with email can later "Continue with Google" to link accounts
- Only providers that verify email ownership are trusted for auto-linking (Google, GitHub)
- Prevents duplicate accounts when users forget which method they used
Authentication Methods
Email & Password
Enable or disable traditional email/password authentication:
emailAndPassword: {
enabled: true, // Set to false to disable
requireEmailVerification: true, // Require email confirmation
}Email Verification
When requireEmailVerification is true, users cannot sign in until they click the verification link sent to their email.
Magic Link
Passwordless authentication via email:
plugins: [
magicLink({
sendMagicLink: async ({ email, url }) => {
await resend.emails.send({
from: "noreply@yourdomain.com",
to: email,
subject: "Sign in to Your App",
react: MagicLinkEmail({ url }),
})
},
}),
]To disable magic links, remove the plugin from the configuration.
Two-Factor Authentication
TOTP-based 2FA (optional):
plugins: [
twoFactor({
issuer: "Your App Name", // Shows in authenticator apps
}),
]When enabled, users can set up 2FA in their account settings.
OAuth Providers
Google OAuth
-
Create credentials at Google Cloud Console
-
Configure redirect URI:
https://yourdomain.com/api/auth/callback/googleFor local development:
http://localhost:3000/api/auth/callback/google -
Add environment variables:
.env.local GOOGLE_CLIENT_ID=your_client_id GOOGLE_CLIENT_SECRET=your_client_secret -
Enable in configuration:
socialProviders: { google: { clientId: env.GOOGLE_CLIENT_ID, clientSecret: env.GOOGLE_CLIENT_SECRET, }, }
GitHub OAuth
-
Create OAuth app at GitHub Developer Settings
-
Configure callback URL:
https://yourdomain.com/api/auth/callback/github -
Add environment variables:
.env.local GITHUB_CLIENT_ID=your_client_id GITHUB_CLIENT_SECRET=your_client_secret -
Enable in configuration:
socialProviders: { github: { clientId: env.GITHUB_CLIENT_ID, clientSecret: env.GITHUB_CLIENT_SECRET, }, }
Environment Variables
| Variable | Required | Description |
|---|---|---|
BETTER_AUTH_SECRET | Yes | Secret for signing session cookies |
DATABASE_URL | Yes | PostgreSQL connection string |
NEXT_PUBLIC_SITE_URL | Yes | Public site URL for email links |
RESEND_API_KEY | Yes* | Email delivery (*if email features enabled) |
GOOGLE_CLIENT_ID | No | Google OAuth client ID |
GOOGLE_CLIENT_SECRET | No | Google OAuth client secret |
GITHUB_CLIENT_ID | No | GitHub OAuth client ID |
GITHUB_CLIENT_SECRET | No | GitHub OAuth client secret |
Generate a secure BETTER_AUTH_SECRET:
openssl rand -base64 32Keep Secrets Safe
Never commit OAuth secrets or BETTER_AUTH_SECRET to version control. Use environment variables in production.
URL Configuration
Customize authentication page paths:
const authConfig = {
urls: {
signIn: "/sign-in",
signUp: "/sign-up",
forgotPassword: "/forgot-password",
resetPassword: "/reset-password",
callback: "/dashboard", // Post-auth redirect
},
}Email Templates
Authentication emails are React components in the emails/ directory:
emails/
├── magic-link.tsx # Magic link sign-in
├── verify-email.tsx # Email verification
└── reset-password.tsx # Password resetCustomize the templates to match your brand. See Email System for details.
Stripe Integration
Better Auth integrates with Stripe to automatically create customers:
plugins: [
stripe({
stripeClient,
stripeWebhookSecret: env.STRIPE_WEBHOOK_SECRET,
createCustomerOnSignUp: true, // Auto-create Stripe customer
}),
]When enabled:
- New users automatically get a Stripe customer ID
- Customer ID stored in
users.stripeCustomerId - Enables seamless checkout and subscription management
See Payments System for details.
Protected Routes
Server Components
import { auth } from "@/lib/auth"
import { headers } from "next/headers"
import { redirect } from "next/navigation"
export default async function DashboardPage() {
const session = await auth.api.getSession({
headers: await headers(),
})
if (!session) {
redirect("/sign-in")
}
return <Dashboard user={session.user} />
}Client Components
"use client"
import { useSession } from "@/lib/auth-client"
import { useRouter } from "next/navigation"
import { useEffect } from "react"
export function ProtectedComponent() {
const { data: session, isPending } = useSession()
const router = useRouter()
useEffect(() => {
if (!isPending && !session) {
router.push("/sign-in")
}
}, [session, isPending, router])
if (isPending) return <Loading />
if (!session) return null
return <Content user={session.user} />
}Middleware (Optional)
For route-level protection:
import { auth } from "@/lib/auth"
import { NextResponse } from "next/server"
import type { NextRequest } from "next/server"
export async function middleware(request: NextRequest) {
const session = await auth.api.getSession({
headers: request.headers,
})
if (!session && request.nextUrl.pathname.startsWith("/dashboard")) {
return NextResponse.redirect(new URL("/sign-in", request.url))
}
return NextResponse.next()
}
export const config = {
matcher: ["/dashboard/:path*"],
}Common Configurations
Simple email/password authentication:
export const auth = betterAuth({
database: drizzleAdapter(db, { provider: "pg", schema }),
emailAndPassword: {
enabled: true,
requireEmailVerification: true,
},
})Social sign-in only (no passwords):
export const auth = betterAuth({
database: drizzleAdapter(db, { provider: "pg", schema }),
emailAndPassword: {
enabled: false,
},
socialProviders: {
google: {
clientId: env.GOOGLE_CLIENT_ID,
clientSecret: env.GOOGLE_CLIENT_SECRET,
},
},
})All authentication methods enabled:
export const auth = betterAuth({
database: drizzleAdapter(db, { provider: "pg", schema }),
emailAndPassword: {
enabled: true,
requireEmailVerification: true,
},
socialProviders: {
google: { /* ... */ },
github: { /* ... */ },
},
plugins: [
magicLink({ /* ... */ }),
twoFactor({ issuer: "Your App" }),
stripe({ /* ... */ }),
],
})Next Steps
- Authentication Flows - Understand the complete user journey
- Email System - Customize email templates
- Payments System - Stripe integration details