Nextauth
Build with NextAuth.js (Auth.js) for authentication in Next.js and other frameworks.
You are an auth specialist who integrates NextAuth.js (Auth.js v5) into projects. Auth.js is an open-source, self-hosted authentication library for Next.js and other frameworks, supporting OAuth, credentials, magic links, and database-backed sessions. ## Key Points - Use JWT strategy for serverless (no database session lookups) - Use database strategy when you need to revoke sessions instantly - Extend the session via callbacks to include user ID and roles - Use middleware for route protection — cleaner than checking in every page - Always set `AUTH_SECRET` — required for token encryption - Use the Credentials provider only with proper password hashing (bcrypt/argon2) - Type-augment the Session and JWT interfaces for type safety - Storing sensitive data in the JWT — it's readable (base64), not encrypted - Using Credentials provider without rate limiting — vulnerable to brute force - Not setting custom pages — the default pages are functional but unstyled - Calling `useSession()` without `<SessionProvider>` — returns undefined - Not handling the loading state — `status` starts as `"loading"` ## Quick Example ```bash npm install next-auth@beta ``` ```env AUTH_SECRET=<random-32-byte-string> AUTH_GOOGLE_ID=your-google-client-id AUTH_GOOGLE_SECRET=your-google-client-secret AUTH_GITHUB_ID=your-github-client-id AUTH_GITHUB_SECRET=your-github-client-secret ```
skilldb get auth-services-skills/NextauthFull skill: 322 linesNextAuth.js (Auth.js) Integration
You are an auth specialist who integrates NextAuth.js (Auth.js v5) into projects. Auth.js is an open-source, self-hosted authentication library for Next.js and other frameworks, supporting OAuth, credentials, magic links, and database-backed sessions.
Core Philosophy
Self-hosted, zero vendor lock-in
Auth.js runs in your application — no external auth service, no per-user pricing. You own the user data, the session storage, and the auth flow. Switch providers or databases without changing your auth code.
Provider-agnostic
Auth.js supports 80+ OAuth providers (Google, GitHub, Discord, etc.), email/magic link, and custom credentials. Adding a provider is a few lines of config. Removing one is deleting those lines.
Adapter pattern
Auth.js uses adapters to persist users and sessions to any database — Prisma, Drizzle, MongoDB, Supabase, DynamoDB. The adapter interface is simple: implement a few functions and your database is wired in.
Setup
Install (Auth.js v5)
npm install next-auth@beta
Environment variables
AUTH_SECRET=<random-32-byte-string>
AUTH_GOOGLE_ID=your-google-client-id
AUTH_GOOGLE_SECRET=your-google-client-secret
AUTH_GITHUB_ID=your-github-client-id
AUTH_GITHUB_SECRET=your-github-client-secret
Configuration
// auth.ts
import NextAuth from 'next-auth';
import Google from 'next-auth/providers/google';
import GitHub from 'next-auth/providers/github';
import Credentials from 'next-auth/providers/credentials';
import { PrismaAdapter } from '@auth/prisma-adapter';
import { prisma } from './lib/prisma';
import bcrypt from 'bcryptjs';
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: PrismaAdapter(prisma),
providers: [
Google,
GitHub,
Credentials({
credentials: {
email: { label: 'Email', type: 'email' },
password: { label: 'Password', type: 'password' },
},
authorize: async (credentials) => {
const user = await prisma.user.findUnique({
where: { email: credentials.email as string },
});
if (!user?.hashedPassword) return null;
const valid = await bcrypt.compare(
credentials.password as string,
user.hashedPassword
);
return valid ? user : null;
},
}),
],
session: { strategy: 'jwt' },
pages: {
signIn: '/login',
error: '/login',
},
});
Route handler
// app/api/auth/[...nextauth]/route.ts
import { handlers } from '@/auth';
export const { GET, POST } = handlers;
Middleware
// middleware.ts
import { auth } from './auth';
export default auth((req) => {
if (!req.auth && req.nextUrl.pathname !== '/login') {
const newUrl = new URL('/login', req.nextUrl.origin);
return Response.redirect(newUrl);
}
});
export const config = {
matcher: ['/dashboard/:path*', '/api/protected/:path*'],
};
Key Techniques
Server-side session
import { auth } from '@/auth';
// In Server Components
export default async function DashboardPage() {
const session = await auth();
if (!session?.user) redirect('/login');
return <div>Welcome {session.user.name}</div>;
}
// In API routes
export async function GET() {
const session = await auth();
if (!session?.user) {
return new Response('Unauthorized', { status: 401 });
}
const data = await prisma.post.findMany({
where: { authorId: session.user.id },
});
return Response.json(data);
}
Client-side session
'use client';
import { useSession, signIn, signOut } from 'next-auth/react';
function AuthButton() {
const { data: session, status } = useSession();
if (status === 'loading') return <div>Loading...</div>;
if (session) {
return (
<div>
<p>{session.user?.email}</p>
<button onClick={() => signOut()}>Sign out</button>
</div>
);
}
return (
<div>
<button onClick={() => signIn('google')}>Sign in with Google</button>
<button onClick={() => signIn('github')}>Sign in with GitHub</button>
</div>
);
}
Session provider (layout)
// app/layout.tsx
import { SessionProvider } from 'next-auth/react';
import { auth } from '@/auth';
export default async function RootLayout({ children }: { children: React.ReactNode }) {
const session = await auth();
return (
<html>
<body>
<SessionProvider session={session}>
{children}
</SessionProvider>
</body>
</html>
);
}
Callbacks (customize tokens and sessions)
export const { handlers, auth, signIn, signOut } = NextAuth({
// ...providers, adapter
callbacks: {
// Add user ID and role to JWT
jwt({ token, user }) {
if (user) {
token.id = user.id;
token.role = user.role;
}
return token;
},
// Add user ID and role to session
session({ session, token }) {
session.user.id = token.id as string;
session.user.role = token.role as string;
return session;
},
// Control who can sign in
signIn({ user, account }) {
if (account?.provider === 'credentials' && !user) return false;
return true;
},
// Control redirects
redirect({ url, baseUrl }) {
if (url.startsWith(baseUrl)) return url;
return baseUrl + '/dashboard';
},
},
});
Type augmentation
// types/next-auth.d.ts
import type { DefaultSession } from 'next-auth';
declare module 'next-auth' {
interface Session {
user: {
id: string;
role: string;
} & DefaultSession['user'];
}
interface User {
role: string;
}
}
declare module 'next-auth/jwt' {
interface JWT {
id: string;
role: string;
}
}
Email/magic link provider
import Resend from 'next-auth/providers/resend';
export const { handlers, auth } = NextAuth({
adapter: PrismaAdapter(prisma),
providers: [
Resend({
from: 'noreply@yourdomain.com',
}),
],
});
Database adapters
// Prisma
import { PrismaAdapter } from '@auth/prisma-adapter';
adapter: PrismaAdapter(prisma)
// Drizzle
import { DrizzleAdapter } from '@auth/drizzle-adapter';
adapter: DrizzleAdapter(db)
// Supabase
import { SupabaseAdapter } from '@auth/supabase-adapter';
adapter: SupabaseAdapter({ url, secret })
// MongoDB
import { MongoDBAdapter } from '@auth/mongodb-adapter';
adapter: MongoDBAdapter(clientPromise)
Server Actions
import { signIn, signOut } from '@/auth';
// Sign in form
export default function LoginPage() {
return (
<form action={async () => {
'use server';
await signIn('google', { redirectTo: '/dashboard' });
}}>
<button type="submit">Sign in with Google</button>
</form>
);
}
Best Practices
- Use JWT strategy for serverless (no database session lookups)
- Use database strategy when you need to revoke sessions instantly
- Extend the session via callbacks to include user ID and roles
- Use middleware for route protection — cleaner than checking in every page
- Always set
AUTH_SECRET— required for token encryption - Use the Credentials provider only with proper password hashing (bcrypt/argon2)
- Type-augment the Session and JWT interfaces for type safety
Anti-Patterns
- Storing sensitive data in the JWT — it's readable (base64), not encrypted
- Using Credentials provider without rate limiting — vulnerable to brute force
- Not setting custom pages — the default pages are functional but unstyled
- Calling
useSession()without<SessionProvider>— returns undefined - Not handling the loading state —
statusstarts as"loading" - Mixing database and JWT strategies without understanding the trade-offs
Install this skill directly: skilldb add auth-services-skills
Related Skills
Auth0
Build with Auth0 for enterprise authentication and identity. Use this skill when
Clerk
Build with Clerk for authentication and user management. Use this skill when the
Cognito
Build with Amazon Cognito for authentication and user management. Use this skill
Descope
Integrate Descope to add secure, no-code/low-code authentication and user management to your applications.
Firebase Auth
Build with Firebase Authentication for user sign-in. Use this skill when the
Hanko
Integrate Hanko for modern, passwordless authentication in your web applications.