How-To Guide
How to Add Secure Authentication to Next.js
Using NextAuth.js (Auth.js) for App Router
TL;DR
TL;DR
Use NextAuth.js for authentication. Set NEXTAUTH_SECRET in production. Use middleware to protect routes. Always verify sessions on API routes too. Check authorization (not just authentication) before accessing resources.
Prerequisites
You'll need a Next.js 13+ app with App Router. This guide uses NextAuth.js v4 (also known as Auth.js).
Step-by-Step Guide
2
Create the auth configuration
Create app/api/auth/[...nextauth]/route.ts:
import NextAuth from 'next-auth';
import GoogleProvider from 'next-auth/providers/google';
import { NextAuthOptions } from 'next-auth';
export const authOptions: NextAuthOptions = {
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
}),
],
callbacks: {
async session({ session, token }) {
// Add user ID to session
if (session.user) {
session.user.id = token.sub!;
}
return session;
},
},
};
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };
3
Set up environment variables
Add to .env.local:
# Generate with: openssl rand -base64 32
NEXTAUTH_SECRET=your-super-secret-key-here
NEXTAUTH_URL=http://localhost:3000
# OAuth provider credentials
GOOGLE_CLIENT_ID=your-client-id
GOOGLE_CLIENT_SECRET=your-client-secret
Critical: NEXTAUTH_SECRET must be set in production. Without it, your sessions are not secure.
4
Protect routes with middleware
Create middleware.ts in your project root:
import { withAuth } from 'next-auth/middleware';
export default withAuth({
callbacks: {
authorized: ({ token }) => !!token,
},
});
export const config = {
matcher: ['/dashboard/:path*', '/settings/:path*', '/api/protected/:path*'],
};
5
Get session in Server Components
import { getServerSession } from 'next-auth';
import { authOptions } from '@/app/api/auth/[...nextauth]/route';
import { redirect } from 'next/navigation';
export default async function DashboardPage() {
const session = await getServerSession(authOptions);
if (!session) {
redirect('/api/auth/signin');
}
return (
<div>
<h1>Welcome, {session.user?.name}</h1>
</div>
);
}
6
Protect API routes
import { getServerSession } from 'next-auth';
import { authOptions } from '@/app/api/auth/[...nextauth]/route';
import { NextResponse } from 'next/server';
export async function GET(request: Request) {
const session = await getServerSession(authOptions);
if (!session) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
// Now check authorization (does this user own this resource?)
const userId = session.user.id;
// Fetch data scoped to this user
const data = await db.posts.findMany({ where: { authorId: userId } });
return NextResponse.json(data);
}
7
Add session provider for client components
Create app/providers.tsx:
'use client';
import { SessionProvider } from 'next-auth/react';
export function Providers({ children }: { children: React.ReactNode }) {
return <SessionProvider>{children}</SessionProvider>;
}
Wrap your app in app/layout.tsx:
import { Providers } from './providers';
export default function RootLayout({ children }) {
return (
<html>
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}
Common Security Mistakes
- Missing NEXTAUTH_SECRET in production - Sessions won't be secure
- Only checking auth on frontend - API routes need auth checks too
- Forgetting authorization - Auth proves who you are, but you still need to check if they can access the resource
- Exposing session data - Don't put sensitive info in the JWT/session that goes to the client
Testing Your Auth
- Try accessing /dashboard without logging in - should redirect
- Try calling API routes without a session - should return 401
- Try accessing another user's data - should be denied
- Check browser DevTools for exposed tokens or sensitive data