December 27, 202510 min read

BetterAuth Setup in Next.js for SaaS Apps

Authentication is the foundation of any SaaS product. It needs to be secure, scalable, and developer-friendly while supporting modern flows like social logins, magic links, and role-based access control (RBAC).

This guide walks you through setting up a production-ready authentication system in Next.js 16+ using BetterAuth a powerful, type-safe auth library built specifically for modern Next.js apps.

SaaSJet uses this exact setup out of the box, so you get email/password, social providers (Google, GitHub), and protected routes from day one.

Why BetterAuth is Perfect for SaaS Builders

  • Full-stack TypeScript – End-to-end type safety with Prisma
  • Flexible providers – Email/password, social logins and more!
  • Built-in RBAC – Roles, organizations, team invites
  • Secure by default – Rate limiting, email verification, secure cookies
  • No lock-in – Open source, self-hostable, easy to customize

Step 1: Install BetterAuth

npm install better-auth @better-auth/prisma

Also install Prisma if you haven't:

npm install prisma @prisma/client
npx prisma init

Step 2: Set Up Your Database Schema

Update your prisma/schema.prisma:

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql" // or "sqlite" for dev
  url      = env("DATABASE_URL")
}

model User {
  id            String    @id @default(cuid())
  email         String    @unique
  emailVerified Boolean   @default(false)
  name          String?
  image         String?
  role          String    @default("user") // admin, user, etc.
  createdAt     DateTime  @default(now())
  sessions      Session[]
  accounts      Account[]
}

model Session {
  id        String   @id
  userId    String
  expiresAt DateTime
  user      User     @relation(fields: [userId], references: [id])
}

model Account {
  id                String   @id @default(cuid())
  userId            String
  provider          String
  providerAccountId String
  user              User     @relation(fields: [userId], references: [id])
  @@unique([provider, providerAccountId])
}

Run migration:

npx prisma migrate dev --name init

Step 3: Initialize BetterAuth

Create src/lib/auth.ts:

import { betterAuth } from "better-auth";
import prisma from "@/lib/prisma";
import { prismaAdapter } from "better-auth/adapters/prisma";

export const auth = betterAuth({
  secret: process.env.BETTER_AUTH_SECRET!,

  database: prismaAdapter(prisma, {
    provider: "postgresql"
  }),

  callbacks: {
    session: async ({ session, user }: any) => {
      // attach custom fields to session.user
      session.user.stripeCustomerId = user.stripeCustomerId;
      return session;
    },
  },

  emailAndPassword: {
    enabled: true,
  },
  socialProviders: {
    github: {
      clientId: process.env.GITHUB_ID!,
      clientSecret: process.env.GITHUB_SECRET!,
    },
    google: {
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    }
  },

})

Add your provider credentials to .env.local:

GOOGLE_CLIENT_ID=your_google_id
GOOGLE_CLIENT_SECRET=your_google_secret
GITHUB_CLIENT_ID=your_github_id
GITHUB_CLIENT_SECRET=your_github_secret
BETTER_AUTH_SECRET=your_strong_random_secret
BETTER_AUTH_URL=http://localhost:3000

Step 4: Create API Routes

BetterAuth automatically generates all routes. Just import the handler:

// src/app/api/auth/[...all]/route.ts

import { auth } from "@/lib/auth";
import { toNextJsHandler } from "better-auth/next-js";

export const { POST, GET } = toNextJsHandler(auth);

That’s it — all auth endpoints are now live at /api/auth/*.

Step 5: Protect Routes (Middleware + Server Components)

Middleware ProtectionCreate middleware.ts:

import { auth } from "@/lib/auth";

export default auth.middleware;

export const config = {
  matcher: ["/dashboard/:path*", "/admin/:path*"],
};

Server-Side Check in Components

import { auth } from "@/lib/auth";

export default async function Dashboard() {
  const session = await auth.api.getSession();

  if (!session.user) {
    redirect("/sign-in");
  }

  return (
    <div>
      <h1>Welcome, {session.user.name}</h1>
      <p>Role: {session.user.role}</p>
    </div>
  );
}

Role-Based Access Control

if (session.user.role !== "admin") {
  notFound(); // or redirect
}

Step 6: Sign-In Page (Client Component)

"use client";

import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { signIn } from "@/lib/auth-client";


export function SignInForm() {
    const [email, setEmail] = useState("");
    const [password, setPassword] = useState("");
    const [loading, setLoading] = useState(false);


    const handleSignIn = async (e: React.FormEvent) => {
        e.preventDefault();
        setLoading(true);

        const res = await signIn.email({
            email,
            password,
            rememberMe: true,
            callbackURL: "/dashboard",
        });

        if (res?.data) {
            console.log("Successfully signed in!", res);
        } else {
            console.log("Sign-in failed:", res?.error);
        }

        setLoading(false);
    };


    return (
        <form onSubmit={handleSignIn} className="space-y-5">
            <Input
                placeholder="Email"
                type="email"
                value={email}
                onChange={(e) => setEmail(e.target.value)}
                className="rounded-xl border-neutral-500"
            />
            <Input
                placeholder="Password"
                type="password"
                value={password}
                onChange={(e) => setPassword(e.target.value)}
                className="rounded-xl border-neutral-500"
            />
            <Button type="submit" variant={"secondary"} className="w-full rounded-xl cursor-pointer" disabled={loading}>
                {loading ? "Signing in..." : "Sign In"}
            </Button>
        </form>
    );
}

Final Thoughts

With BetterAuth, you get a complete, secure, and extensible authentication system in minutes no need to build sessions, verification flows, or OAuth from scratch.

SaaSJet ships with all of this fully configured: sign-in pages, protected routes, admin guards, and user management dashboard so you can focus on building your product, not auth plumbing.

Build this faster with SaaSJet → clone the repo saasjet
Happy coding! Published: December 27, 2025