Next.js authentication patterns masterclass
Next.js Authentication Patterns Masterclass
Introduction
Authentication is a critical aspect of modern web applications, ensuring that users can securely access their data while keeping unauthorized users out. Next.js, with its hybrid rendering capabilities (SSR, SSG, and CSR), offers multiple ways to implement authentication, each with its own trade-offs.
In this masterclass, we’ll explore the most effective authentication patterns for Next.js, covering:
- Session-based authentication with NextAuth.js
- Token-based authentication (JWT) with API routes
- Middleware-based authentication in Next.js 13+
- Third-party authentication providers (OAuth, Auth0, Firebase)
By the end, you'll have a clear understanding of how to implement secure, scalable authentication in your Next.js applications.
1. Session-Based Authentication with NextAuth.js
NextAuth.js is the go-to library for session-based authentication in Next.js. It simplifies OAuth integration, email/password logins, and database sessions while supporting JWT for stateless authentication.
Setting Up NextAuth.js
First, install the package:
npm install next-auth
Next, configure pages/api/auth/[...nextauth].js
:
import NextAuth from "next-auth";
import GitHubProvider from "next-auth/providers/github";
export default NextAuth({
providers: [
GitHubProvider({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
}),
],
secret: process.env.NEXTAUTH_SECRET,
});
Protecting Routes
Use the useSession
hook to check authentication status in client components:
import { useSession, signIn, signOut } from "next-auth/react"; export default function Dashboard() { const { data: session } = useSession(); if (!session) { return ( <button onClick={() => signIn("github")}> Sign in with GitHub </button> ); } return ( <> <p>Welcome, {session.user.name}!</p> <button onClick={() => signOut()}>Sign out</button> </> ); }
For server-side pages, use getServerSession
:
import { getServerSession } from "next-auth"; import { authOptions } from "./api/auth/[...nextauth]"; export async function getServerSideProps(context) { const session = await getServerSession(context.req, context.res, authOptions); if (!session) { return { redirect: { destination: "/login", permanent: false } }; } return { props: { session } }; }
2. Token-Based Authentication (JWT) with API Routes
For applications requiring stateless authentication (e.g., mobile clients), JWT (JSON Web Tokens) is a popular choice.
Generating and Validating JWTs
Install jsonwebtoken
:
npm install jsonwebtoken
Create a login API route (pages/api/login.js
):
import jwt from "jsonwebtoken"; export default function handler(req, res) { if (req.method !== "POST") return res.status(405).end(); const { username, password } = req.body; // Validate credentials (e.g., against a database) if (username !== "admin" || password !== "password") { return res.status(401).json({ error: "Invalid credentials" }); } const token = jwt.sign( { username }, process.env.JWT_SECRET, { expiresIn: "1h" } ); res.status(200).json({ token }); }
Securing API Routes
Use middleware to validate JWTs in protected routes:
import jwt from "jsonwebtoken"; export default function handler(req, res) { const token = req.headers.authorization?.split(" ")[1]; if (!token) return res.status(401).json({ error: "Unauthorized" }); try { const decoded = jwt.verify(token, process.env.JWT_SECRET); res.status(200).json({ data: "Protected data", user: decoded.username }); } catch (err) { res.status(401).json({ error: "Invalid token" }); } }
3. Middleware-Based Authentication in Next.js 13+
Next.js 13 introduced middleware for running logic before a request completes. This is ideal for authentication checks.
Creating Middleware
Add a middleware.js
file in your project root:
import { NextResponse } from "next/server"; import { getToken } from "next-auth/jwt"; export async function middleware(req) { const token = await getToken({ req, secret: process.env.NEXTAUTH_SECRET }); if (!token) { return NextResponse.redirect(new URL("/login", req.url)); } return NextResponse.next(); } export const config = { matcher: ["/dashboard/:path*"], };
This redirects unauthenticated users from /dashboard/*
to /login
.
4. Third-Party Authentication Providers
For applications that prefer not to manage credentials, third-party providers like Auth0 or Firebase offer robust solutions.
Example: Firebase Authentication
Install Firebase:
npm install firebase
Initialize Firebase in a utility file:
import { initializeApp } from "firebase/app"; import { getAuth } from "firebase/auth"; const firebaseConfig = { apiKey: process.env.FIREBASE_API_KEY, authDomain: process.env.FIREBASE_AUTH_DOMAIN, projectId: process.env.FIREBASE_PROJECT_ID, }; const app = initializeApp(firebaseConfig); export const auth = getAuth(app);
Use Firebase in a login component:
import { auth } from "@/lib/firebase"; import { signInWithEmailAndPassword } from "firebase/auth"; export default function Login() { const handleLogin = async () => { try { await signInWithEmailAndPassword(auth, "user@example.com", "password"); } catch (error) { console.error(error); } }; return <button onClick={handleLogin}>Sign in with Firebase</button>; }
Conclusion
Next.js provides multiple authentication strategies, each suited for different use cases:
- NextAuth.js for session-based auth with minimal setup.
- JWT for stateless, API-driven applications.
- Middleware for edge-based authentication in Next.js 13+.
- Third-party providers for managed authentication solutions.
Choose the pattern that aligns with your security requirements, scalability needs, and development workflow. By leveraging these techniques, you can build secure, performant authentication flows in Next.js with confidence.
For further reading, explore the Next.js Authentication Docs and NextAuth.js Documentation. Happy coding! 🚀