Skip to content

eagerworks/better-auth-nextjs-demo

Repository files navigation

Better Auth Next.js Demo

A comprehensive authentication demo showcasing Better Auth with Next.js 15 and Prisma. This project demonstrates modern authentication patterns using a simple car collection example to show how Better Auth handles multi-tenant data, 2FA, and organization management.

✨ Core Authentication Features

  • πŸ” Email & Password Authentication - Essential auth functionality
  • πŸ”„ Session Management - Server-side sessions with automatic handling
  • πŸšͺ Route Protection - Middleware-based protection
  • πŸ“Š Type Safety - Full TypeScript integration
  • 🎨 Clean UI - Modern, responsive components
  • πŸ”— Social Authentication - GitHub OAuth integration
  • πŸ”‘ Password Management - Set password for OAuth users

🏒 Organization Plugin Integration

This demo showcases Better Auth's Organization plugin with multi-tenant architecture:

  • Plugin-based Architecture - Added with a single import and configuration
  • Database Schema Generation - Prisma integration handled automatically
  • Organization Context - User sessions include active organization data
  • Data Isolation - Server-side filtering ensures organization data separation
  • Member Management - Invite members with different roles (owner, admin, member)
  • Organization Switching - Users can switch between organizations seamlessly

πŸ”’ Two-Factor Authentication (2FA)

Enterprise-grade Two-Factor Authentication (2FA) powered by Better Auth's built-in TOTP support:

  • TOTP Integration - Compatible with Google Authenticator, Authy, and other TOTP apps
  • Backup Codes - Recovery codes for account access if device is lost
  • User-friendly Setup - QR code generation and step-by-step verification
  • Session Security - Enhanced protection for sensitive operations
  • Easy Management - Enable/disable 2FA with password confirmation

πŸš— Example Domain: Car Collection

A simple car collection example to demonstrate real-world multi-tenant patterns:

  • Basic CRUD Operations - Add, edit, delete cars with form validation
  • Hierarchical Data - Brands β†’ Models β†’ Cars structure
  • Organization Scoping - Data properly isolated by organization
  • Brand Assignment - Assign shared brands to specific organizations
  • Real-time Updates - Server actions with instant UI updates

This example showcases how Better Auth's organization plugin enables proper data isolation in multi-tenant applications.

πŸ”§ Technical Implementation

  • Server Actions - All mutations through secure server actions
  • Session Validation - Every action validates user session
  • Type Safety - End-to-end TypeScript with Prisma types
  • Form Validation - Comprehensive Zod schemas
  • Error Handling - Proper error states and user feedback

🎯 Purpose

This repository demonstrates:

  • Production-Ready Authentication - Complete auth system with enterprise features
  • Multi-Tenant Architecture - Proper data isolation and organization management
  • Modern Full-Stack Development - Next.js 15, Prisma, TypeScript best practices
  • Security Best Practices - 2FA, session management, data validation

πŸš€ Quick Start

Prerequisites

  • Node.js 18+
  • PostgreSQL database
  • pnpm (recommended)

Setup

git clone <your-repo>
cd better-auth-nextjs-demo
pnpm install

Environment

Copy env.example to .env.local:

cp env.example .env.local

Required environment variables:

# Database
DATABASE_URL="postgresql://..."

# Better Auth
BETTER_AUTH_SECRET="your-secret-key"  # Generate with: openssl rand -base64 32
BETTER_AUTH_URL="http://localhost:3000"

# GitHub OAuth (optional)
GITHUB_CLIENT_ID="your-github-client-id"
GITHUB_CLIENT_SECRET="your-github-client-secret"

Database

Choose your preferred option:

Supabase (Free):

  1. Create account at supabase.com
  2. Get connection string from Settings β†’ Database
  3. Update DATABASE_URL in .env.local

Local Docker:

docker-compose up -d

Initialize

pnpx prisma generate
pnpx prisma db push
pnpm run db:seed  # Optional: add sample data
pnpm dev

Visit http://localhost:3000

πŸ“ Project Structure

src/
β”œβ”€β”€ app/
β”‚   β”œβ”€β”€ api/auth/[...all]/route.ts  # Better Auth API endpoint
β”‚   β”œβ”€β”€ dashboard/page.tsx          # Main dashboard with examples
β”‚   β”œβ”€β”€ sign-in/page.tsx           # Authentication pages
β”‚   └── sign-up/page.tsx
β”œβ”€β”€ components/
β”‚   β”œβ”€β”€ auth/                      # Authentication components
β”‚   β”‚   β”œβ”€β”€ organization-settings.tsx  # Organization management
β”‚   β”‚   β”œβ”€β”€ two-factor-settings.tsx    # 2FA setup/management
β”‚   β”‚   └── set-password-form.tsx      # Password setting for OAuth
β”‚   β”œβ”€β”€ cars/                      # Example domain components
β”‚   β”‚   β”œβ”€β”€ add-car-form.tsx          # CRUD form examples
β”‚   β”‚   β”œβ”€β”€ edit-car-dialog.tsx       # Modal examples
β”‚   β”‚   └── assign-brand-form.tsx     # Multi-tenant examples
β”‚   └── ui/                        # Reusable UI components
β”œβ”€β”€ lib/
β”‚   β”œβ”€β”€ auth.ts                    # Server auth config with plugins
β”‚   β”œβ”€β”€ actions.ts                 # Server actions for all operations
β”‚   β”œβ”€β”€ data.ts                    # Database queries with org context
β”‚   β”œβ”€β”€ validations.ts             # Zod schemas for forms
β”‚   └── types.ts                   # TypeScript type definitions
└── middleware.ts                   # Route protection

πŸ”§ Key Implementation Details

Authentication Configuration

export const auth = betterAuth({
  database: prismaAdapter(prisma, {
    provider: "postgresql",
  }),
  emailAndPassword: {
    enabled: true,
    requireEmailVerification: false,
    autoSignIn: true,
  },
  socialProviders: {
    github: {
      clientId: process.env.GITHUB_CLIENT_ID!,
      clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    },
  },
  plugins: [
    twoFactor({
      issuer: "Better Auth Demo",
      totpOptions: {
        period: 30,
        digits: 6,
      },
    }),
    organization({
      async sendInvitationEmail(data) {
        // Custom email sending logic
      },
    }),
  ],
  session: {
    expiresIn: 60 * 60 * 24 * 7, // 7 days
    updateAge: 60 * 60 * 24, // 1 day
  },
});

Multi-Tenant Data Access

// All queries respect organization context
export async function getCars(organizationId?: string | null) {
  return await prisma.car.findMany({
    where: {
      organizationId: organizationId || null,
    },
    include: {
      model: { include: { brand: true } },
      organization: true,
    },
  });
}

Better Auth Client Methods (Recommended)

For authentication operations, use Better Auth's secure client methods instead of custom server actions:

// auth-client.ts - Export only methods that are actually used
export const twoFactor = {
  enable: authClient.twoFactor.enable,
  disable: authClient.twoFactor.disable,
  verifyTotp: authClient.twoFactor.verifyTotp,
};

export const organization = {
  create: authClient.organization.create,
  setActive: authClient.organization.setActive,
  inviteMember: authClient.organization.inviteMember,
};

Pattern 1: Try-Catch (when you don't need response data)

const handleCreateOrganization = async (formData: FormData) => {
  setIsLoading(true);
  try {
    await organization.create({ name, slug });
    toast.success("Organization created successfully!");
    router.refresh();
  } catch (error: any) {
    toast.error(error.message || "Failed to create organization");
  } finally {
    setIsLoading(false);
  }
};

Pattern 2: { data, error } (when you need response data)

const handleEnable2FA = async (password: string) => {
  try {
    const { data, error } = await twoFactor.enable({ password });
    if (error) {
      toast.error(error.message);
      return;
    }
    // Use the data
    setQrCodeUri(data.totpURI);
    setBackupCodes(data.backupCodes);
  } catch (error: any) {
    toast.error(error.message);
  }
};

Benefits:

  • βœ… Built-in CSRF protection
  • βœ… Secure cookie management
  • βœ… Proper session validation
  • βœ… Rate limiting and security headers
  • βœ… Cleaner code with better error handling

Server Actions with Validation

For non-auth operations, continue using server actions:

export async function addCarAction(
  prevState: ActionResult,
  formData: FormData
): Promise<ActionResult> {
  // Session validation
  await validateSession();

  // Zod validation
  const validationResult = addCarSchema.safeParse(data);

  // Database operation
  const car = await createCar(validationResult.data);

  // UI revalidation
  revalidatePath("/dashboard");

  return { success: true, data: car };
}

🚒 Deployment

pnpm build
pnpx prisma migrate deploy

Deploy to Vercel, Netlify, or your preferred platform. Make sure to set all environment variables in your deployment platform.

πŸ’‘ What This Demo Shows

  • Modern Authentication - Better Auth as a complete auth solution
  • Multi-Tenant Patterns - How to properly isolate data by organization
  • Server-first Architecture - Leveraging Next.js 15's server capabilities
  • Type Safety - End-to-end TypeScript with Better Auth + Prisma
  • Enterprise Features - 2FA, organizations, member management made simple

πŸ“š Learn More

πŸ“„ License

MIT License - perfect for learning and experimentation!

About

Next.js + Better-auth + Prisma demo with complete authentication flow and route protection.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published