Back to Knowledge
Updated Sep 2026

Authentication

Login, signup, sessions, OAuth

OAuth

A secure way to log in using another account (like Google or GitHub) without sharing your password with the app.

"Like a hotel key card. The front desk (Google) vouches for you, so the room (app) lets you in."

: the wall between your app and the world. Here's the 2026 landscape, how to pick, and copy-paste setups for Next.js 16.

1Who Are You, and What Can You Do?

Two different questions, two different words. Authentication

Authentication

Verifying WHO you are, usually by logging in with email and password, a magic link, or OAuth with Google/GitHub. Libraries like Auth.js, Better Auth, and Clerk handle the hard parts.

"Like showing your ID at the door. Proving you are who you claim to be."

asks "who are you?" (the ID check at the door). Authorization

Authorization

Verifying WHAT you can do. After you're authenticated, authorization checks if you have permission for a specific action.

"Like having a building pass but only for certain floors. You're in, but not everywhere."

asks "are you allowed to do this?" (the VIP list). An auth library handles the first. The second is your code, on every request that touches data.

User accounts

Save preferences, history, data.

Protected routes

Dashboards, admin, APIs.

Billing

Tie subscriptions to a real person.

2Your Options in 2026

The big news

In September 2025 the Auth.js project became part of Better Auth. Auth.js v5 still works and is still maintained, but the maintainers now recommend Better Auth for new projects. If your AI assistant reaches for NextAuth by reflex, that's training data talking.

Better AuthNEW PROJECTS

Open-source, TypeScript-first, runs in your app against your database

Open source

Best for

  • • Owning your users table
  • • Email/password + social + plugins
  • • No per-user pricing
  • • The recommended starting point from the team that now maintains Auth.js

Watch out for

  • • You build the sign-in UI
  • • You run the database and migrations
  • • More moving parts than a hosted service

Install

npm install better-auth

Auth.js v5 (NextAuth)STILL BETA

The long-time Next.js default, now part of Better Auth

Open source

Best for

  • • Existing NextAuth apps
  • • Quick OAuth-only login
  • • Huge amount of tutorials and examples

Watch out for

  • • v5 is still published under the beta tag
  • • npm install next-auth gives you v4
  • • For brand-new apps, its maintainers point you to Better Auth

Install

npm install next-auth@beta

ClerkFASTEST

Hosted auth with polished drop-in UI components

Hobby: 50,000 MRU per app

Best for

  • • Shipping login in minutes
  • • Pre-built sign-in, profile, org UI
  • • Teams/organizations and MFA without building them

Watch out for

  • • User data lives on Clerk's servers
  • • Per-user pricing beyond the free tier
  • • Vendor lock-in if you migrate later

Install

npx -y clerk@latest init

Supabase AuthWITH SUPABASE

Auth bundled with a Supabase Postgres database

Free plan: 50,000 MAU

Best for

  • • You already use Supabase
  • • Row Level Security tied to the logged-in user
  • • One dashboard for DB + auth + storage

Watch out for

  • • Tied to the Supabase ecosystem
  • • RLS has a learning curve
  • • Free projects pause after a week of inactivity

Install

npm install @supabase/supabase-js @supabase/ssr

Auth0, Firebase Auth, and friends

Enterprise-grade hosted options. Reach for them when you need enterprise SSO (SAML, Okta), complex multi-tenant identity, or you're already deep in Google's mobile stack. Free tiers exist; check each vendor's current pricing page before committing, because per-user costs add up at scale.

Free-tier numbers as of Sep 2026. Check Clerk pricing and Supabase pricing for today's limits. (MRU = monthly retained users; MAU = monthly active users.)

3Quick Decision Guide

If you want...Use
Login working today, beautiful UI, don't care where users liveClerk
Own your users table in your own Postgres (e.g. Neon), no per-user feesBetter Auth
You already run NextAuth / Auth.js and it worksStay on Auth.js v5
Your database is SupabaseSupabase Auth
Enterprise SSO (SAML, Okta) is a hard requirementClerk or Auth0

Saucy take: hosted (Clerk) trades money and control for speed. Self-hosted libraries (Better Auth, Auth.js) trade setup time for ownership. Both are fine. What's not fine is building your own password system.

4Setup: Auth.js v5 + GitHub

The shortest path to "Sign in with GitHub" in a Next.js 16 app. Also the setup behind a lot of existing codebases, so worth knowing.

1

Install the v5 beta and generate a secret

Terminal

npm install next-auth@beta
npx auth secret   # writes AUTH_SECRET to .env.local

Plain npm install next-auth installs v4, which uses different imports and env var names. Check your package.json says 5.0.0-beta.x.

2

Create auth.ts at the project root

auth.ts

import NextAuth from "next-auth";
import GitHub from "next-auth/providers/github";

export const { handlers, signIn, signOut, auth } = NextAuth({
  providers: [GitHub],
});

No clientId needed: v5 reads AUTH_GITHUB_ID and AUTH_GITHUB_SECRET automatically. That magic is also the #1 way it breaks (see the traps below).

3

Add the route handler

app/api/auth/[...nextauth]/route.ts

import { handlers } from "@/auth";
export const { GET, POST } = handlers;
4

Set environment variables

.env.local

AUTH_SECRET=generated-by-npx-auth-secret
AUTH_GITHUB_ID=your_github_oauth_client_id
AUTH_GITHUB_SECRET=your_github_oauth_client_secret

Pattern: AUTH_{PROVIDER}_ID / AUTH_{PROVIDER}_SECRET. On Vercel you usually don't need AUTH_URL or AUTH_TRUST_HOST; v5 works out the host from the request. Add the same values in Vercel (see Environment Variables).

5

Register the callback URL with GitHub

Callback URL

http://localhost:3000/api/auth/callback/github
https://yourdomain.com/api/auth/callback/github

Getting this wrong is the OAuth Trap. Read that guide before you deploy.

6

Add a sign-in button (Server Action, no client JS)

components/SignIn.tsx

import { signIn } from "@/auth";

export function SignIn() {
  return (
    <form
      action={async () => {
        "use server";
        await signIn("github");
      }}
    >
      <button type="submit">Sign in with GitHub</button>
    </form>
  );
}
7

Optional: a proxy.ts for fast redirects

proxy.ts

export { auth as proxy } from "@/auth";

This is proxy.ts, not middleware.ts, in Next.js 16. It's a convenience, not your security. Real checks go in server components, route handlers, and Server Actions.

5Setup: Better Auth (Own Your Users)

Better Auth runs inside your app and stores users, sessions, and accounts in your database. Here's the shape of it with Postgres + Drizzle.

1

Install

Terminal

npm install better-auth @better-auth/drizzle-adapter
2

Environment variables

.env.local

BETTER_AUTH_SECRET=a-random-string-of-32-plus-characters
BETTER_AUTH_URL=http://localhost:3000
GITHUB_CLIENT_ID=...
GITHUB_CLIENT_SECRET=...
3

Configure lib/auth.ts

lib/auth.ts

import { betterAuth } from "better-auth";
import { drizzleAdapter } from "@better-auth/drizzle-adapter";
import { db } from "@/lib/db";

export const auth = betterAuth({
  database: drizzleAdapter(db, { provider: "pg" }),
  emailAndPassword: { enabled: true },
  socialProviders: {
    github: {
      clientId: process.env.GITHUB_CLIENT_ID as string,
      clientSecret: process.env.GITHUB_CLIENT_SECRET as string,
    },
  },
});
4

Mount the handler and generate tables

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

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

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

Terminal

npx auth@latest generate   # creates the auth tables for your schema

GitHub callback URL: /api/auth/callback/github. Include the user:email scope or users with private emails will fail to sign in. Full client-side usage lives in the Better Auth docs.

6Setup: Clerk (Fastest Path)

1

Let the CLI do it

Terminal

npx -y clerk@latest init

It detects Next.js, installs @clerk/nextjs, and writes NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY and CLERK_SECRET_KEY to .env.local.

2

What it sets up: the provider

app/layout.tsx

import { ClerkProvider } from "@clerk/nextjs";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <ClerkProvider>{children}</ClerkProvider>
      </body>
    </html>
  );
}
3

What it sets up: proxy.ts

proxy.ts (Next.js 16)

import { clerkMiddleware } from "@clerk/nextjs/server";

export default clerkMiddleware();

export const config = {
  matcher: [
    "/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)",
    "/(api|trpc)(.*)",
    "/__clerk/(.*)",
  ],
};

Yes, the function is still called clerkMiddleware; only the file name changed. Clerk's own docs say to protect data "as close to the resource as possible": call await auth() from @clerk/nextjs/server where you read or write data.

4

Drop in the UI

components/Navbar.tsx

import { SignInButton, UserButton } from "@clerk/nextjs";

// Show SignInButton to guests, UserButton to signed-in users
<SignInButton />
<UserButton />

Verify the install anytime with npx -y clerk@latest doctor.

7Sign-In Methods

Email + password

Universal, but you now own password resets, breach checks, and verification emails.

Best for: any app, especially B2B

Magic link / email code

Passwordless. Needs a reliable email sender.

Best for: low-friction consumer signup

Social OAuth

"Sign in with Google/GitHub." One click for users, five config steps for you.

Best for: consumer apps, dev tools. See OAuth Providers

Passkeys / WebAuthn

Face ID, fingerprint, or a hardware key. Phishing-resistant.

Best for: security-sensitive apps, as a second option

8Common Traps

The OAuth Trap

Hit redirect_uri_mismatch or client_id=undefined? Deep dive into the 5-part OAuth setup.

Installing v4 by accident

npm install next-auth gives you v4. Then v5 docs, v5 imports, and AUTH_ env names don't line up with what you installed.

Fix: Use next-auth@beta for v5, or pick Better Auth for a new project.

v4-style env var names on a v5 app

Old guides use GITHUB_ID, NEXTAUTH_SECRET, NEXTAUTH_URL. v5 auto-reads AUTH_GITHUB_ID and AUTH_SECRET. Mismatch means login silently sends client_id=undefined.

Fix: Rename to the AUTH_{PROVIDER}_ID convention everywhere: .env.local, Vercel Production, and Vercel Preview.

"proxy.ts protects my app"

Proxy (old middleware) is an optimistic, early check. A matcher tweak or a refactor can quietly skip routes, and Server Actions are just POSTs to the page they live on.

Fix: Re-check the session wherever data is read or changed. That's the core lesson of Protected Routes.

"I'll build auth myself"

Password hashing, session rotation, CSRF, rate limiting, account recovery: each one is a place to get owned.

Fix: Use a maintained library or service. Spend the saved week on your actual product.

Tokens in localStorage

Any script on the page (including an XSS payload) can read localStorage.

Fix: Keep sessions in httpOnly cookies. Every option above does this by default; don't undo it.

Trusting IDs from the URL

/invoices/123 lets anyone try /invoices/124.

Fix: Always check that the signed-in user owns the record in the query itself (WHERE user_id = session user).

Users can log in?

Lock down what they can reach, then start charging for it.

Stuck mid-setup? Book a session.