Auth.js v5 vs Lucia v3 vs Better Auth 2026
Auth Is Not Solved — It Is Just Complicated Differently
Authentication in Next.js in 2026 means choosing between three competing philosophies: Auth.js (the established standard), Lucia (the "bring your own database" approach), and Better Auth (the comprehensive newcomer). Each makes different trade-offs.
The right choice depends on your stack, your complexity tolerance, and what you are building.
TL;DR
- Auth.js v5 (NextAuth): Choose for OAuth-first apps (Google, GitHub login), rapid prototypes, and teams that know it. Established, broad adapter support.
- Lucia v3: Choose for full control over session management and database schema. Best for apps that need custom auth flows.
- Better Auth: Choose for feature-complete auth with organizations, 2FA, and magic links out of the box. The rising standard.
- Clerk: Ignore for self-hosted; use for managed auth when you want to pay to not think about auth ($25+/mo).
Key Takeaways
- Auth.js v5 is the most used (200K+ weekly npm installs) but v5 was a breaking rewrite
- Lucia was deprecated in September 2024 as a library — the author now recommends it as a "learning resource" and advocates for the patterns, not the package
- Better Auth launched in 2024 and has grown rapidly (~50K weekly downloads); the most feature-complete self-hosted option
- All three support Next.js App Router server components and server actions
- Better Auth is the current recommendation for new projects in 2026
Auth.js v5 (NextAuth)
Auth.js v5 is the fifth major version of NextAuth.js. It introduced edge compatibility, server actions support, and a redesigned API.
Setup
npm install next-auth@beta @auth/prisma-adapter
// auth.ts — v5 configuration:
import NextAuth from 'next-auth';
import { PrismaAdapter } from '@auth/prisma-adapter';
import Google from 'next-auth/providers/google';
import Resend from 'next-auth/providers/resend';
import { db } from './lib/db';
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: PrismaAdapter(db),
providers: [
Google({
clientId: process.env.AUTH_GOOGLE_ID!,
clientSecret: process.env.AUTH_GOOGLE_SECRET!,
}),
Resend({
from: 'noreply@yourapp.com',
}),
],
callbacks: {
session({ session, user }) {
session.user.id = user.id;
session.user.role = user.role;
return session;
},
},
});
// app/api/auth/[...nextauth]/route.ts:
export { handlers as GET, handlers as POST };
// Using auth in server components:
import { auth } from '@/auth';
export default async function DashboardPage() {
const session = await auth();
if (!session) redirect('/login');
return <div>Hello, {session.user.name}</div>;
}
// Using auth in server actions:
export async function updateProfile(data: FormData) {
'use server';
const session = await auth();
if (!session) throw new Error('Unauthorized');
await db.user.update({
where: { id: session.user.id },
data: { name: data.get('name') as string },
});
}
Auth.js v5 Strengths
- 20+ built-in OAuth providers (Google, GitHub, Discord, Apple, etc.)
- Active community and extensive documentation
- Prisma, Drizzle, MongoDB adapters maintained
- Magic links (email) built-in
- JWTs or database sessions
Auth.js v5 Weaknesses
- v5 is still in beta (as of March 2026) — API may change
- No built-in organization/team support
- No built-in 2FA
- Session refresh logic can be tricky
Lucia v3 (Reference Implementation)
Lucia v3 is no longer recommended as a production library. In September 2024, the author deprecated it and shifted to providing auth as educational patterns. The Lucia approach is still valid as architecture guidance.
The Lucia pattern: implement sessions yourself using the concepts Lucia pioneered.
// The "Lucia pattern" — manual session management:
import { sha256 } from '@oslojs/crypto/sha2';
import { encodeBase32LowerCaseNoPadding, encodeHexLowerCase } from '@oslojs/encoding';
import { db } from './db';
export function generateSessionToken(): string {
const bytes = new Uint8Array(20);
crypto.getRandomValues(bytes);
return encodeBase32LowerCaseNoPadding(bytes);
}
export async function createSession(token: string, userId: string) {
const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
const session = {
id: sessionId,
userId,
expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 30), // 30 days
};
await db.session.create({ data: session });
return session;
}
export async function validateSessionToken(token: string) {
const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
const session = await db.session.findUnique({
where: { id: sessionId },
include: { user: true },
});
if (!session || Date.now() >= session.expiresAt.getTime()) {
if (session) await db.session.delete({ where: { id: sessionId } });
return { session: null, user: null };
}
// Refresh session if expiring in less than 15 days:
if (Date.now() >= session.expiresAt.getTime() - 1000 * 60 * 60 * 24 * 15) {
session.expiresAt = new Date(Date.now() + 1000 * 60 * 60 * 24 * 30);
await db.session.update({
where: { id: sessionId },
data: { expiresAt: session.expiresAt },
});
}
return { session, user: session.user };
}
Use the Lucia patterns if: You want full control and understand auth deeply. Not recommended for teams who want to ship fast.
Better Auth (Recommended for New Projects)
Better Auth launched in 2024 as a comprehensive auth solution with features that others charge for or lack entirely.
npm install better-auth
// auth.ts — Better Auth configuration:
import { betterAuth } from 'better-auth';
import { prismaAdapter } from 'better-auth/adapters/prisma';
import { organization } from 'better-auth/plugins';
import { twoFactor } from 'better-auth/plugins';
import { db } from './lib/db';
export const auth = betterAuth({
database: prismaAdapter(db, { provider: 'postgresql' }),
emailAndPassword: { enabled: true },
socialProviders: {
google: { clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET! },
github: { clientId: process.env.GITHUB_CLIENT_ID!, clientSecret: process.env.GITHUB_CLIENT_SECRET! },
},
plugins: [
organization(), // Teams, roles, invitations
twoFactor(), // TOTP 2FA
],
});
// Route handler:
// app/api/auth/[...all]/route.ts:
import { auth } from '@/auth';
import { toNextJsHandler } from 'better-auth/next-js';
export const { GET, POST } = toNextJsHandler(auth);
// Client-side usage:
import { createAuthClient } from 'better-auth/react';
import { organizationClient } from 'better-auth/client/plugins';
import { twoFactorClient } from 'better-auth/client/plugins';
export const { signIn, signOut, useSession, organization } = createAuthClient({
baseURL: process.env.NEXT_PUBLIC_URL!,
plugins: [organizationClient(), twoFactorClient()],
});
// In a React component:
const { data: session } = useSession();
// Sign in with organization support:
await signIn.social({ provider: 'google', callbackURL: '/dashboard' });
// Create an organization:
await organization.create({ name: 'Acme Corp', slug: 'acme' });
Better Auth Strengths
- Organizations/teams built-in — roles, permissions, invitations
- Two-factor authentication (TOTP) built-in
- Magic links built-in
- Passkeys support
- Rate limiting on auth endpoints
- Active development with weekly releases
- TypeScript-first with excellent type inference
Better Auth Weaknesses
- Newer library — smaller community than Auth.js
- Documentation still maturing
- Fewer examples in the wild
Feature Comparison
| Feature | Auth.js v5 | Lucia Pattern | Better Auth |
|---|---|---|---|
| OAuth providers | 20+ built-in | Manual | 10+ built-in |
| Email/password | Via provider | Manual | Built-in |
| Magic links | Via Resend provider | Manual | Built-in |
| Organizations/teams | No | Manual | Built-in plugin |
| Two-factor auth | No | Manual | Built-in plugin |
| Passkeys | No | Manual | Built-in |
| Session handling | Automatic | Manual | Automatic |
| Drizzle ORM | Yes | Yes | Yes |
| Prisma ORM | Yes | Yes | Yes |
| Edge compatible | Yes | Yes | Yes |
| Weekly downloads | ~200K | Deprecated | ~50K |
Which Boilerplates Use Which?
| Boilerplate | Default Auth |
|---|---|
| ShipFast | Auth.js (NextAuth) |
| Makerkit | Better Auth or Supabase Auth |
| Supastarter | Supabase Auth |
| SaaSBold | Auth.js |
| OpenSaaS | Wasp auth (Passport.js) |
| T3 Stack | Auth.js or NextAuth |
| Midday v1 | Supabase Auth |
Recommendation
For new projects in 2026: Better Auth.
It provides the most features without sacrificing developer experience. Organizations, 2FA, and magic links are common SaaS requirements. Better Auth includes all three. Auth.js is still a solid choice if your team knows it well, but for new projects, Better Auth is the better starting point.
For existing projects: Keep Auth.js unless you need organizations or 2FA. Migration from Auth.js to Better Auth is non-trivial.
For maximum control: Use the Lucia patterns (implement sessions yourself using the published algorithms) if you need deep customization.
Methodology
Based on publicly available documentation from Auth.js, Lucia, and Better Auth, npm download statistics, and community discussions as of March 2026.
Choosing an auth strategy for your SaaS? StarterPick helps you find boilerplates pre-configured with the right auth library for your needs.