Auth.js (the project formerly known as NextAuth) is a flexible auth library for Next.js. It supports OAuth providers (Google, GitHub, etc.), credentials auth, and database-backed sessions. With the App Router, you use route handlers and Server Component integration.
What Auth.js provides
| Feature | Description |
|---|---|
| OAuth providers | Google, GitHub, Discord, and 50+ others |
| Credentials auth | Email/password with your own logic |
| Session strategies | JWT (stateless) or database (stateful) |
| Callbacks | Customize JWT, session, and sign-in behavior |
| TypeScript support | Full types for session and configuration |
Auth.js handles the OAuth flow, session management, and CSRF protection. You configure providers and callbacks.
Installation and setup
1. Install Auth.js
npm install next-auth@beta
Use the beta for App Router support, or check the current stable version.
2. Create the route handler
Create app/api/auth/[...nextauth]/route.ts:
import NextAuth from 'next-auth';
import { authConfig } from '@/lib/auth';
const handler = NextAuth(authConfig);
export { handler as GET, handler as POST };
3. Create the auth config
Create lib/auth.ts:
import type { NextAuthConfig } from 'next-auth';
import Google from 'next-auth/providers/google';
import GitHub from 'next-auth/providers/github';
export const authConfig: NextAuthConfig = {
providers: [
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!,
}),
],
callbacks: {
async jwt({ token, user }) {
if (user) {
token.id = user.id;
}
return token;
},
async session({ session, token }) {
if (token.id) {
session.user.id = token.id as string;
}
return session;
},
},
pages: {
signIn: '/login',
error: '/auth/error',
},
};
4. Add environment variables
AUTH_SECRET=your-random-secret-at-least-32-chars
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secret
Generate AUTH_SECRET with openssl rand -base64 32.
OAuth providers
Auth.js supports 50+ OAuth providers. Each needs client credentials:
import Google from 'next-auth/providers/google';
import GitHub from 'next-auth/providers/github';
import Discord from 'next-auth/providers/discord';
providers: [
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!,
}),
Discord({
clientId: process.env.DISCORD_CLIENT_ID!,
clientSecret: process.env.DISCORD_CLIENT_SECRET!,
}),
],
Set up OAuth apps in each provider's developer console and add the callback URL: https://yourdomain.com/api/auth/callback/google.
Credentials authentication
For email/password auth, use the Credentials provider:
import Credentials from 'next-auth/providers/credentials';
import { verifyPassword } from '@/lib/password';
import { getUserByEmail } from '@/lib/db';
providers: [
Credentials({
name: 'Email',
credentials: {
email: { label: 'Email', type: 'email' },
password: { label: 'Password', type: 'password' },
},
async authorize(credentials) {
if (!credentials?.email || !credentials?.password) {
return null;
}
const user = await getUserByEmail(credentials.email as string);
if (!user) {
return null;
}
const isValid = await verifyPassword(
credentials.password as string,
user.passwordHash
);
if (!isValid) {
return null;
}
return {
id: user.id,
email: user.email,
name: user.name,
};
},
}),
],
Never store plain passwords. Use bcrypt or Argon2 for hashing.
Session strategies
JWT sessions (default)
Session data is encoded in a cookie. No database required.
session: {
strategy: 'jwt',
maxAge: 30 * 24 * 60 * 60, // 30 days
},
Database sessions
Sessions are stored in a database. Supports revocation.
import { PrismaAdapter } from '@auth/prisma-adapter';
import { prisma } from '@/lib/prisma';
adapter: PrismaAdapter(prisma),
session: {
strategy: 'database',
},
| Strategy | Pros | Cons |
|---|---|---|
| JWT | No database needed, fast | Cannot revoke, larger cookies |
| Database | Revocable, smaller cookies | Requires database, slower |
Getting the session
In Server Components
import { auth } from '@/lib/auth';
export default async function ProfilePage() {
const session = await auth();
if (!session) {
redirect('/login');
}
return (
<div>
<h1>Welcome, {session.user?.name}</h1>
<p>Email: {session.user?.email}</p>
</div>
);
}
In Route Handlers
import { auth } from '@/lib/auth';
import { NextResponse } from 'next/server';
export async function GET() {
const session = await auth();
if (!session) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
return NextResponse.json({ user: session.user });
}
In Client Components
'use client';
import { useSession } from 'next-auth/react';
export function UserMenu() {
const { data: session, status } = useSession();
if (status === 'loading') {
return <div>Loading...</div>;
}
if (!session) {
return <LoginButton />;
}
return <div>Welcome, {session.user?.name}</div>;
}
Wrap your app with SessionProvider:
// app/providers.tsx
'use client';
import { SessionProvider } from 'next-auth/react';
export function Providers({ children }: { children: React.ReactNode }) {
return <SessionProvider>{children}</SessionProvider>;
}
Protecting routes
With middleware
Create middleware.ts:
import { auth } from '@/lib/auth';
import { NextResponse } from 'next/server';
export default auth((req) => {
const isLoggedIn = !!req.auth;
const isProtectedRoute = req.nextUrl.pathname.startsWith('/dashboard');
if (isProtectedRoute && !isLoggedIn) {
return NextResponse.redirect(new URL('/login', req.url));
}
return NextResponse.next();
});
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};
In layouts
import { auth } from '@/lib/auth';
import { redirect } from 'next/navigation';
export default async function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
const session = await auth();
if (!session) {
redirect('/login');
}
return <div>{children}</div>;
}
Callbacks
Add custom fields to session
callbacks: {
async jwt({ token, user, account }) {
// Add user ID and role on sign-in
if (user) {
token.id = user.id;
token.role = user.role;
}
return token;
},
async session({ session, token }) {
// Make them available in session
session.user.id = token.id as string;
session.user.role = token.role as string;
return session;
},
},
Control sign-in
callbacks: {
async signIn({ user, account, profile }) {
// Only allow specific email domains
if (user.email?.endsWith('@company.com')) {
return true;
}
return false; // Reject sign-in
},
},
Type augmentation
Extend session types for custom fields:
// types/next-auth.d.ts
import { DefaultSession } from 'next-auth';
declare module 'next-auth' {
interface Session {
user: {
id: string;
role: string;
} & DefaultSession['user'];
}
}
Summary
Auth.js provides flexible authentication for Next.js:
- Install next-auth and create the route handler
- Configure providers (OAuth, credentials)
- Choose session strategy (JWT or database)
- Get session with
auth()in Server Components - Protect routes with middleware or layout checks
- Customize with callbacks for custom fields
The Auth.js docs have the full API and provider list.
