Next.js Auth (Auth.js v5)
Rules
- Centralize config in
auth.ts (NextAuth() export: handlers, auth, signIn, signOut). Never scatter provider secrets.
- Protect routes in
middleware.ts (edge-safe auth wrapper), and re-check session inside Server Actions/mutations — middleware is UX, not authorization.
- Read session in Server Components via
await auth(). In Client Components use useSession() from next-auth/react with <SessionProvider>.
- Never expose secrets (
AUTH_SECRET, provider keys) with NEXT_PUBLIC_ prefix. Validate env with zod at boot.
- Role checks: extend session type via module augmentation (
user.role), enforce on server.
Patterns
import NextAuth from 'next-auth';
import GitHub from 'next-auth/providers/github';
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [GitHub],
callbacks: {
async session({ session, token }) {
if (session.user) (session.user as { role?: string }).role = token.role as string;
return session;
},
async jwt({ token, user }) {
if (user) token.role = (user as { role?: string }).role ?? 'user';
return token;
},
},
});
export { auth as middleware } from '@/auth';
export const config = { matcher: ['/dashboard/:path*', '/admin/:path*'] };
import { handlers } from '@/auth';
export const { GET, POST } = handlers;
import { auth } from '@/auth';
import { redirect } from 'next/navigation';
export default async function AdminPage() {
const session = await auth();
if (!session?.user) redirect('/login');
if ((session.user as { role?: string }).role !== 'admin')
redirect('/dashboard');
return <AdminView />;
}
import 'next-auth';
declare module 'next-auth' {
interface User { role?: string }
}
Checklist