Quick Start

Add authentication to your Next.js app in 10 minutes using Better Auth.

This guide shows you how to add authentication to a Next.js application using Better Auth with Transactional Auth as your identity provider.

Prerequisites

  • A Transactional account (sign up free)
  • A Next.js 14+ application
  • PostgreSQL database (for session storage)

Step 1: Create a Transactional Auth Application

  1. Go to your Transactional Dashboard
  2. Navigate to Auth > Applications
  3. Click Create Application
  4. Configure:
    • Name: Your app name
    • Type: Server
    • Redirect URIs: https://yourapp.com/api/auth/callback/transactional
    • Allowed Origins: https://yourapp.com
  5. Copy your Client ID and Client Secret

Note: For local development, also add http://localhost:3000/api/auth/callback/transactional and http://localhost:3000.

Step 2: Install Better Auth

npm install better-auth

Step 3: Configure Environment Variables

Create .env.local:

# Your app URL
BETTER_AUTH_URL=http://localhost:3000
NEXT_PUBLIC_APP_URL=http://localhost:3000
 
# Transactional Auth (use your app's Auth Domain from the dashboard)
TRANSACTIONAL_AUTH_URL=https://your-app-name-abc123.auth.usetransactional.com
TRANSACTIONAL_CLIENT_ID=your_client_id
TRANSACTIONAL_CLIENT_SECRET=your_client_secret
 
# Database for Better Auth sessions
DATABASE_URL=postgresql://user:password@localhost:5432/myapp

Step 4: Create Auth Configuration

Create lib/auth.ts:

import { betterAuth } from "better-auth";
import { genericOAuth } from "better-auth/plugins";
 
export const auth = betterAuth({
  baseURL: process.env.BETTER_AUTH_URL!,
  database: {
    provider: "postgresql",
    url: process.env.DATABASE_URL!,
  },
  plugins: [
    genericOAuth({
      config: [
        {
          providerId: "transactional",
          clientId: process.env.TRANSACTIONAL_CLIENT_ID!,
          clientSecret: process.env.TRANSACTIONAL_CLIENT_SECRET!,
          authorizationUrl: `${process.env.TRANSACTIONAL_AUTH_URL}/auth`,
          tokenUrl: `${process.env.TRANSACTIONAL_AUTH_URL}/token`,
          userInfoUrl: `${process.env.TRANSACTIONAL_AUTH_URL}/me`,
          scopes: ["openid", "profile", "email"],
          pkce: true,
        },
      ],
    }),
  ],
});

Step 5: Create API Route

Create app/api/auth/[...all]/route.ts:

import { auth } from "@/lib/auth";
import { toNextJsHandler } from "better-auth/next-js";
 
export const { GET, POST } = toNextJsHandler(auth.handler);

Step 6: Create Auth Client

Create lib/auth-client.ts:

import { createAuthClient } from "better-auth/react";
 
export const authClient = createAuthClient({
  baseURL: process.env.NEXT_PUBLIC_APP_URL,
});
 
export const { signIn, signOut, useSession } = authClient;

Step 7: Run Database Migration

Create the required tables:

npx better-auth migrate

Step 8: Add Login Button

Create components/auth-buttons.tsx:

"use client";
 
import { signIn, signOut, useSession } from "@/lib/auth-client";
 
export function LoginButton() {
  return (
    <button
      onClick={() => signIn.social({
        provider: "transactional",
        callbackURL: "/dashboard"
      })}
      className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
    >
      Sign in
    </button>
  );
}
 
export function LogoutButton() {
  return (
    <button
      onClick={() => signOut({ callbackURL: "/" })}
      className="px-4 py-2 bg-gray-200 rounded-lg hover:bg-gray-300"
    >
      Sign out
    </button>
  );
}
 
export function AuthStatus() {
  const { data: session, isPending } = useSession();
 
  if (isPending) return <div>Loading...</div>;
 
  if (session) {
    return (
      <div className="flex items-center gap-4">
        <span>Welcome, {session.user.name || session.user.email}</span>
        <LogoutButton />
      </div>
    );
  }
 
  return <LoginButton />;
}

Step 9: Protect Pages

Client Component (with hook)

"use client";
 
import { useSession } from "@/lib/auth-client";
import { redirect } from "next/navigation";
 
export default function DashboardPage() {
  const { data: session, isPending } = useSession();
 
  if (isPending) return <div>Loading...</div>;
  if (!session) redirect("/login");
 
  return (
    <div>
      <h1>Dashboard</h1>
      <p>Email: {session.user.email}</p>
      <p>Name: {session.user.name}</p>
    </div>
  );
}

Server Component

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("/login");
 
  return (
    <div>
      <h1>Dashboard</h1>
      <p>Email: {session.user.email}</p>
    </div>
  );
}

Step 10: Create Login Page

Create app/login/page.tsx:

import { LoginButton } from "@/components/auth-buttons";
 
export default function LoginPage() {
  return (
    <div className="min-h-screen flex items-center justify-center">
      <div className="text-center">
        <h1 className="text-2xl font-bold mb-8">Welcome</h1>
        <LoginButton />
      </div>
    </div>
  );
}

Test Your Integration

  1. Start your app: npm run dev
  2. Visit http://localhost:3000/login
  3. Click "Sign in" - you'll be redirected to Transactional Auth
  4. Create an account or sign in
  5. After authentication, you'll be redirected to /dashboard

Production Deployment

Update your environment variables for production:

BETTER_AUTH_URL=https://yourapp.com
NEXT_PUBLIC_APP_URL=https://yourapp.com
TRANSACTIONAL_AUTH_URL=https://auth.usetransactional.com
DATABASE_URL=postgresql://user:password@prod-host:5432/myapp

And update your Transactional Auth application with production URLs.

What's Stored Where?

DataLocationManaged By
User accountsTransactional AuthTransactional
PasswordsTransactional AuthTransactional
MFA settingsTransactional AuthTransactional
SessionsYour databaseBetter Auth
OAuth tokensYour databaseBetter Auth

Next Steps