Next.js authentication patterns
Introduction
Authentication is a critical aspect of modern web applications, and Next.js provides several flexible patterns to implement secure authentication flows. Whether you're building a traditional server-rendered application, a hybrid app, or a fully client-side SPA, Next.js offers solutions that align with your architecture. In this post, we'll explore the most effective Next.js authentication patterns, their trade-offs, and practical implementations.
Session-Based Authentication with NextAuth.js
One of the most popular approaches for authentication in Next.js is using session-based authentication with NextAuth.js. This library simplifies the process of adding authentication to your Next.js application while supporting various providers (OAuth, email/password, etc.).
Basic Setup with NextAuth.js
First, install NextAuth.js:
npm install next-auth
Then, create an API route for authentication (typically at pages/api/auth/[...nextauth].js
):
import NextAuth from 'next-auth'
import Providers from 'next-auth/providers'
export default NextAuth({
providers: [
Providers.GitHub({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
}),
],
database: process.env.DATABASE_URL,
secret: process.env.SECRET,
})
Protecting Routes
You can protect routes using NextAuth.js's session management:
import { getSession } from 'next-auth/client' export async function getServerSideProps(context) { const session = await getSession(context) if (!session) { return { redirect: { destination: '/api/auth/signin', permanent: false, }, } } return { props: { session } } }
JWT Authentication with API Routes
For applications that require more control over the authentication flow, JSON Web Tokens (JWT) can be implemented directly with Next.js API routes.
JWT Implementation
Here's a basic JWT implementation:
import jwt from 'jsonwebtoken' import { compare } from 'bcryptjs' export default async function handler(req, res) { if (req.method !== 'POST') return res.status(405).end() const { email, password } = req.body // Validate user credentials const user = await getUserByEmail(email) if (!user || !(await compare(password, user.password))) { return res.status(401).json({ error: 'Invalid credentials' }) } // Create JWT const token = jwt.sign( { userId: user.id }, process.env.JWT_SECRET, { expiresIn: '1h' } ) res.setHeader('Set-Cookie', `token=${token}; HttpOnly; Path=/; Max-Age=3600`) res.status(200).json({ success: true }) }
Protecting API Routes
To protect API routes, create a middleware function:
export function withAuth(handler) { return async (req, res) => { const token = req.cookies.token try { const decoded = jwt.verify(token, process.env.JWT_SECRET) req.userId = decoded.userId return handler(req, res) } catch (err) { return res.status(401).json({ error: 'Unauthorized' }) } } }
Third-Party Authentication with OAuth
Next.js works well with third-party OAuth providers like Auth0, Firebase Auth, or Supabase Auth.
Auth0 Integration Example
First, install the Auth0 SDK:
npm install @auth0/nextjs-auth0
Then configure it in your _app.js
:
import { UserProvider } from '@auth0/nextjs-auth0' function MyApp({ Component, pageProps }) { return ( <UserProvider> <Component {...pageProps} /> </UserProvider> ) } export default MyApp
Using Auth0 in Components
Access user information in your components:
import { useUser } from '@auth0/nextjs-auth0' export default function Profile() { const { user, error, isLoading } = useUser() if (isLoading) return <div>Loading...</div> if (error) return <div>{error.message}</div> return user ? ( <div> <h2>{user.name}</h2> <p>{user.email}</p> </div> ) : ( <a href="/api/auth/login">Login</a> ) }
Edge Authentication with Middleware
Next.js 12+ introduced middleware that runs at the edge, allowing for authentication checks before the page loads.
Authentication Middleware Example
Create 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 path = req.nextUrl.pathname const protectedPaths = ['/dashboard', '/profile'] const isProtected = protectedPaths.some(p => path.startsWith(p)) if (isProtected) { const token = await getToken({ req, secret: process.env.NEXTAUTH_SECRET }) if (!token) { const url = new URL('/api/auth/signin', req.url) url.searchParams.set('callbackUrl', req.url) return NextResponse.redirect(url) } } return NextResponse.next() }
Conclusion
Next.js offers multiple authentication patterns to suit different application requirements. Whether you choose session-based authentication with NextAuth.js, JWT tokens, third-party providers like Auth0, or edge middleware, each approach has its strengths. Consider your application's architecture, security requirements, and user experience when selecting an authentication pattern.
For most applications, NextAuth.js provides the best balance of convenience and flexibility, while custom JWT implementations offer more control for specific use cases. The new middleware feature in Next.js 12+ opens up exciting possibilities for edge authentication, reducing latency and improving performance.
Remember to always follow security best practices, such as using HTTPS, secure cookie settings, and proper token expiration times, regardless of which authentication pattern you choose.