Next.js Authentication - Complete Implementation Guide
Mahesh Waghmare Implementing authentication in Next.js requires understanding both client-side and server-side authentication patterns. This guide covers multiple authentication approaches, from simple JWT to comprehensive solutions like NextAuth.js.
Authentication Overview
Next.js supports multiple authentication patterns:
- Server-side: API routes, Server Components, Middleware
- Client-side: React components, hooks
- Hybrid: Combination of both
Common Approaches:
- NextAuth.js - Complete authentication solution
- JWT Tokens - Custom JWT implementation
- Session-based - Server-side sessions
- OAuth - Third-party authentication
- Custom - Build your own solution
Key Considerations:
- Secure token storage
- Server-side validation
- Protected routes
- API route protection
- Session management
NextAuth.js Setup
NextAuth.js is the most popular authentication solution for Next.js.
Installation
npm install next-auth
Basic Setup
1. Create API Route (app/api/auth/[...nextauth]/route.ts):
import NextAuth from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';
const handler = NextAuth({
providers: [
CredentialsProvider({
name: 'Credentials',
credentials: {
username: { label: 'Username', type: 'text' },
password: { label: 'Password', type: 'password' }
},
async authorize(credentials) {
// Validate credentials
if (credentials?.username === 'admin' && credentials?.password === 'password') {
return { id: '1', name: 'Admin', email: 'admin@example.com' };
}
return null;
}
})
],
pages: {
signIn: '/auth/signin',
},
callbacks: {
async jwt({ token, user }) {
if (user) {
token.id = user.id;
}
return token;
},
async session({ session, token }) {
if (session.user) {
session.user.id = token.id as string;
}
return session;
}
}
});
export { handler as GET, handler as POST };
2. Environment Variables (.env.local):
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=your-secret-key-here
3. Session Provider (app/layout.tsx):
import { SessionProvider } from 'next-auth/react';
export default function RootLayout({ children }) {
return (
<html>
<body>
<SessionProvider>
{children}
</SessionProvider>
</body>
</html>
);
}
4. Use in Components:
'use client';
import { useSession, signIn, signOut } from 'next-auth/react';
export default function Component() {
const { data: session, status } = useSession();
if (status === 'loading') return <p>Loading...</p>;
if (session) {
return (
<>
<p>Signed in as {session.user?.email}</p>
<button onClick={() => signOut()}>Sign out</button>
</>
);
}
return (
<>
<p>Not signed in</p>
<button onClick={() => signIn()}>Sign in</button>
</>
);
}
JWT Authentication
Custom JWT Implementation
1. Install Dependencies:
npm install jsonwebtoken bcryptjs
npm install -D @types/jsonwebtoken @types/bcryptjs
2. Login API Route (app/api/auth/login/route.ts):
import { NextRequest, NextResponse } from 'next/server';
import jwt from 'jsonwebtoken';
import bcrypt from 'bcryptjs';
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
export async function POST(request: NextRequest) {
const { email, password } = await request.json();
// Validate user (check database)
const user = await getUserByEmail(email);
if (!user) {
return NextResponse.json({ error: 'Invalid credentials' }, { status: 401 });
}
// Verify password
const isValid = await bcrypt.compare(password, user.password);
if (!isValid) {
return NextResponse.json({ error: 'Invalid credentials' }, { status: 401 });
}
// Generate JWT
const token = jwt.sign(
{ userId: user.id, email: user.email },
JWT_SECRET,
{ expiresIn: '7d' }
);
const response = NextResponse.json({ success: true });
response.cookies.set('token', token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
maxAge: 60 * 60 * 24 * 7 // 7 days
});
return response;
}
3. Verify Token Middleware (middleware.ts):
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import jwt from 'jsonwebtoken';
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
export function middleware(request: NextRequest) {
const token = request.cookies.get('token')?.value;
if (!token) {
return NextResponse.redirect(new URL('/login', request.url));
}
try {
jwt.verify(token, JWT_SECRET);
return NextResponse.next();
} catch (error) {
return NextResponse.redirect(new URL('/login', request.url));
}
}
export const config = {
matcher: ['/dashboard/:path*', '/admin/:path*']
};
Session Management
Server-Side Sessions
1. Session Storage (using database):
// lib/session.ts
import { cookies } from 'next/headers';
import { SignJWT, jwtVerify } from 'jose';
const secret = new TextEncoder().encode(process.env.JWT_SECRET);
export async function createSession(userId: string) {
const session = await new SignJWT({ userId })
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
.setExpirationTime('7d')
.sign(secret);
cookies().set('session', session, {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 60 * 60 * 24 * 7
});
}
export async function getSession() {
const session = cookies().get('session')?.value;
if (!session) return null;
try {
const { payload } = await jwtVerify(session, secret);
return payload;
} catch {
return null;
}
}
export async function deleteSession() {
cookies().delete('session');
}
2. Use in Server Components:
import { getSession } from '@/lib/session';
import { redirect } from 'next/navigation';
export default async function ProtectedPage() {
const session = await getSession();
if (!session) {
redirect('/login');
}
return <div>Protected Content</div>;
}
OAuth Providers
NextAuth.js with OAuth
Setup Google OAuth:
import GoogleProvider from 'next-auth/providers/google';
export default NextAuth({
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
})
],
});
Setup GitHub OAuth:
import GitHubProvider from 'next-auth/providers/github';
export default NextAuth({
providers: [
GitHubProvider({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
})
],
});
Environment Variables:
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
GITHUB_ID=your-github-id
GITHUB_SECRET=your-github-secret
Protected Routes
Using Middleware
Middleware Protection (middleware.ts):
import { withAuth } from 'next-auth/middleware';
export default withAuth({
pages: {
signIn: '/auth/signin',
},
});
export const config = {
matcher: ['/dashboard/:path*', '/admin/:path*']
};
Server Component Protection
import { getServerSession } from 'next-auth/next';
import { redirect } from 'next/navigation';
import { authOptions } from '@/app/api/auth/[...nextauth]/route';
export default async function ProtectedPage() {
const session = await getServerSession(authOptions);
if (!session) {
redirect('/auth/signin');
}
return <div>Protected Content</div>;
}
Client Component Protection
'use client';
import { useSession } from 'next-auth/react';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';
export default function ProtectedComponent() {
const { data: session, status } = useSession();
const router = useRouter();
useEffect(() => {
if (status === 'unauthenticated') {
router.push('/auth/signin');
}
}, [status, router]);
if (status === 'loading') {
return <div>Loading...</div>;
}
if (!session) {
return null;
}
return <div>Protected Content</div>;
}
API Route Protection
Protect API Routes
import { getServerSession } from 'next-auth/next';
import { NextResponse } from 'next/server';
import { authOptions } from '../auth/[...nextauth]/route';
export async function GET(request: Request) {
const session = await getServerSession(authOptions);
if (!session) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
// Protected API logic
return NextResponse.json({ data: 'Protected data' });
}
JWT Verification in API Routes
import { NextRequest, NextResponse } from 'next/server';
import jwt from 'jsonwebtoken';
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
export async function GET(request: NextRequest) {
const token = request.cookies.get('token')?.value;
if (!token) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
try {
const decoded = jwt.verify(token, JWT_SECRET);
// Use decoded.userId for database queries
return NextResponse.json({ data: 'Protected data' });
} catch (error) {
return NextResponse.json({ error: 'Invalid token' }, { status: 401 });
}
}
Best Practices
Security
- Use HTTPS in production
- HttpOnly Cookies for tokens
- Secure Cookies in production
- Strong Secrets for JWT signing
- Token Expiration - set reasonable expiry times
- Password Hashing - use bcrypt with salt rounds
- Rate Limiting - prevent brute force attacks
Performance
- Server Components for authentication checks
- Middleware for route protection
- Session Caching - cache session data
- Token Refresh - implement refresh tokens
User Experience
- Loading States - show loading during auth checks
- Error Handling - clear error messages
- Redirect After Login - return to intended page
- Remember Me - optional longer sessions
Conclusion
Next.js authentication can be implemented using:
- NextAuth.js - Easiest, most feature-complete
- JWT - Custom, flexible solution
- Sessions - Server-side session management
- OAuth - Third-party authentication
Key Points:
- Use NextAuth.js for quick setup
- Implement JWT for custom needs
- Protect routes with middleware
- Secure API routes
- Follow security best practices
Choose the approach that fits your needs and security requirements.
Written by Mahesh Waghmare
I bridge the gap between WordPress architecture and modern React frontends. Currently building tools for the AI era.
Follow on Twitter →