Vibestacks LogoVibestacks
Authentication

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:

lib/auth.ts
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:

config/app.ts
// Your site URL is automatically trusted
// In development, localhost:3000 is also trusted
lib/auth.ts
const 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:

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:

config/app.ts
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
    },
  },
}
SettingDefaultDescription
expiresIn7 daysHow long until session expires completely
updateAge1 daySession expiry refreshes after this much activity
cookieCache.enabledtrueReduces database calls by ~80%
cookieCache.maxAge5 minHow 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):

config/app.ts
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.

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

  1. Create credentials at Google Cloud Console

  2. Configure redirect URI:

    https://yourdomain.com/api/auth/callback/google

    For local development:

    http://localhost:3000/api/auth/callback/google
  3. Add environment variables:

    .env.local
    GOOGLE_CLIENT_ID=your_client_id
    GOOGLE_CLIENT_SECRET=your_client_secret
  4. Enable in configuration:

    socialProviders: {
      google: {
        clientId: env.GOOGLE_CLIENT_ID,
        clientSecret: env.GOOGLE_CLIENT_SECRET,
      },
    }

GitHub OAuth

  1. Create OAuth app at GitHub Developer Settings

  2. Configure callback URL:

    https://yourdomain.com/api/auth/callback/github
  3. Add environment variables:

    .env.local
    GITHUB_CLIENT_ID=your_client_id
    GITHUB_CLIENT_SECRET=your_client_secret
  4. Enable in configuration:

    socialProviders: {
      github: {
        clientId: env.GITHUB_CLIENT_ID,
        clientSecret: env.GITHUB_CLIENT_SECRET,
      },
    }

Environment Variables

VariableRequiredDescription
BETTER_AUTH_SECRETYesSecret for signing session cookies
DATABASE_URLYesPostgreSQL connection string
NEXT_PUBLIC_SITE_URLYesPublic site URL for email links
RESEND_API_KEYYes*Email delivery (*if email features enabled)
GOOGLE_CLIENT_IDNoGoogle OAuth client ID
GOOGLE_CLIENT_SECRETNoGoogle OAuth client secret
GITHUB_CLIENT_IDNoGitHub OAuth client ID
GITHUB_CLIENT_SECRETNoGitHub OAuth client secret

Generate a secure BETTER_AUTH_SECRET:

openssl rand -base64 32

Keep 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 reset

Customize 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:

middleware.ts
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,
    },
  },
})

Next Steps