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.
- π 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
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
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
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.
- 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
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
- Node.js 18+
- PostgreSQL database
- pnpm (recommended)
git clone <your-repo>
cd better-auth-nextjs-demo
pnpm installCopy env.example to .env.local:
cp env.example .env.localRequired 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"Choose your preferred option:
Supabase (Free):
- Create account at supabase.com
- Get connection string from Settings β Database
- Update
DATABASE_URLin.env.local
Local Docker:
docker-compose up -dpnpx prisma generate
pnpx prisma db push
pnpm run db:seed # Optional: add sample data
pnpm devVisit http://localhost:3000
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
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
},
});// 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,
},
});
}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
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 };
}pnpm build
pnpx prisma migrate deployDeploy to Vercel, Netlify, or your preferred platform. Make sure to set all environment variables in your deployment platform.
- 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
- Better Auth Documentation
- Better Auth Organization Plugin
- Better Auth 2FA Plugin
- Next.js 15 Documentation
- Prisma Documentation
MIT License - perfect for learning and experimentation!