MENU
Authentication Libraries
Hand-rolling authentication is fine for a simple, credentials-only app, but it stops being fine the moment you need OAuth providers, password reset flows, email verification, or multi-factor authentication — each of those is a small security-critical protocol in its own right, and getting one wrong is easy. That's the gap authentication libraries fill: they've already implemented the fiddly, high-stakes parts, tested them against real providers, and exposed a much smaller surface for you to configure.
Auth.js (formerly NextAuth.js)
Auth.js is the longest-established authentication library built specifically for the Next.js ecosystem, though it has since grown framework-agnostic adapters too. It supports dozens of OAuth providers out of the box, credentials-based login, and email "magic link" sign-in, and it can run sessions either as encrypted JWTs (no database required) or backed by a database adapter (Prisma, Drizzle, and others) for revocable sessions. In the App Router it's wired up through a single catch-all Route Handler and a shared configuration object, and it integrates with Middleware for route protection.
Clerk
Clerk is a hosted authentication service rather than a self-hosted library: user records, sessions, and multi-factor authentication all live on Clerk's infrastructure, and your app talks to it through an SDK. In exchange for that dependency, you get prebuilt, drop-in UI components (<SignIn/>, <UserButton/>, <SignedIn/>), a clerkMiddleware() helper for protecting routes, and a dashboard for managing users without writing an admin panel yourself. It's a strong fit when you'd rather not run your own user database at all.
Lucia
Lucia was a lightweight, framework-agnostic session and auth toolkit that favored low-level, copy-into-your-project primitives over a large configuration object, giving you direct control over your user and session tables. As of 2024 the Lucia project shifted away from being a maintained npm package and now positions itself primarily as a learning resource and set of reference guides for implementing auth by hand — worth reading for the concepts even if you reach for a different library to actually install.
Better Auth
Better Auth is a newer, framework-agnostic, TypeScript-first authentication library with a plugin architecture covering organizations, multi-factor authentication, and other add-ons beyond the core email/password and OAuth flows. Like Auth.js, it's self-hosted — your own database stores the user and session records — but it's built from scratch around modern App Router patterns rather than having grown up in the Pages Router era.
When to reach for a library instead of hand-rolling
- You need one or more OAuth providers — the token exchange, CSRF-state, and PKCE handling are easy to get subtly wrong.
- You need password reset, email verification, or multi-factor authentication — each is its own small protocol with real security consequences if implemented carelessly.
- You want a maintained, audited codebase handling credentials instead of a bespoke one only you have reviewed.
- You'd rather not run user infrastructure at all — a hosted option like Clerk removes the database entirely.
Hand-rolling remains reasonable when your requirements are genuinely simple (a single credentials form, no third-party providers) and you want to avoid an external dependency and its configuration surface.
Configuration and secrets
Every one of these libraries needs secrets — OAuth client ids and client secrets, a session-signing secret, a service API key — and none of them should ever be exposed to the browser. Store them in .env.local and read them with process.env on the server only; in Next.js, only variables explicitly prefixed with NEXT_PUBLIC_ are inlined into client-side JavaScript, so simply omitting that prefix keeps a secret server-only. Add .env*.local to .gitignore and configure the equivalent secrets through your hosting provider's environment variable settings for deployed environments.
auth.ts:
import NextAuth from 'next-auth'
import GitHub from 'next-auth/providers/github'
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
GitHub({
clientId: process.env.AUTH_GITHUB_ID,
clientSecret: process.env.AUTH_GITHUB_SECRET,
}),
],
})app,api,auth,[...nextauth],route.ts:
import { handlers } from '@/auth'
export const { GET, POST } = handlers