Auth0 Next.js SDK
Authentication Library
Auth0 Next.js SDK
Overview
Auth0 Next.js SDK is Auth0's official authentication library designed specifically for Next.js applications, fully supporting Next.js 15 and App Router as of 2025. It provides authentication across all rendering modes including Server-Side Rendering (SSR), Static Site Generation (SSG), and Client-Side Rendering (CSR), server-side authentication in API Routes, protected route implementation through Middleware integration, and Edge Runtime support. With comprehensive support for OAuth 2.0, OpenID Connect, PKCE (Proof Key for Code Exchange), automatic token management, session management, and React Server Components integration, it enables comprehensive implementation of authentication for modern full-stack Next.js applications.
Details
Auth0 Next.js SDK v4 supports both Next.js App Router and Pages Router, providing unified management of server-side and client-side authentication. The architecture consists of four main components: Auth Provider, session management, API Route handlers, and Middleware integrations, enabling full-stack authentication. Features include consistent authentication state management across React Server Components and Client Components, efficient route protection through Next.js Middleware, high-speed authentication processing optimized for Edge Runtime, and type-safe development with full TypeScript support. Integration with SWR and TanStack Query, declarative authentication through custom hooks, and automated error handling maximize developer experience.
Key Features
- Next.js 15/App Router Support: Complete integration with latest Next.js features
- Full-stack Authentication: Unified authentication experience across SSR/SSG/CSR
- Middleware Integration: Efficient route protection and redirection
- Server Components: Authentication state management in React Server Components
- Edge Runtime: High-speed authentication processing at the edge
- TypeScript Support: Complete type safety and excellent developer experience
Advantages and Disadvantages
Advantages
- Deep integration with framework as Next.js official partner
- Consistent authentication experience across all SSR/SSG/CSR rendering modes
- Efficient route protection leveraging latest App Router features
- Global high-speed authentication through Edge Runtime support
- Development-time type safety guarantee with full TypeScript support
- Zero-hydration authentication in React Server Components
Disadvantages
- Next.js platform exclusive, cannot be used with other React frameworks
- Vendor lock-in risk due to Auth0 service dependency
- Costs based on user count and request volume in commercial usage
- Complexity when implementing custom authentication flows
- Session management overhead in large-scale applications
- Debugging difficulties due to Auth0 dashboard configuration errors
Reference Pages
Usage Examples
Installation and Setup
# Create Next.js project
npx create-next-app@latest my-auth0-app --typescript --tailwind --eslint --app
# Install Auth0 Next.js SDK
cd my-auth0-app
npm install @auth0/nextjs-auth0
# Create environment variables file
touch .env.local
# .env.local - Environment Variables
AUTH0_SECRET='your-long-secret-key-here' # Generated with openssl rand -hex 32
AUTH0_BASE_URL='http://localhost:3000'
AUTH0_ISSUER_BASE_URL='https://your-tenant.auth0.com'
AUTH0_CLIENT_ID='your-client-id'
AUTH0_CLIENT_SECRET='your-client-secret'
AUTH0_AUDIENCE='https://your-api-identifier' # For API (optional)
App Router Configuration (Next.js 13+)
// app/layout.tsx - Root Layout and Auth Provider
import { UserProvider } from '@auth0/nextjs-auth0/client';
import { Inter } from 'next/font/google';
import './globals.css';
const inter = Inter({ subsets: ['latin'] });
export const metadata = {
title: 'Auth0 Next.js App',
description: 'Next.js application with Auth0 authentication',
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body className={inter.className}>
<UserProvider>
<div className="min-h-screen bg-gray-50">
<nav className="bg-white shadow">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between h-16">
<div className="flex items-center">
<h1 className="text-xl font-semibold">My App</h1>
</div>
<div className="flex items-center space-x-4">
<LoginButton />
</div>
</div>
</div>
</nav>
<main className="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
{children}
</main>
</div>
</UserProvider>
</body>
</html>
);
}
// components/LoginButton.tsx - Login Button Component
'use client';
import { useUser } from '@auth0/nextjs-auth0/client';
import Link from 'next/link';
export default function LoginButton() {
const { user, error, isLoading } = useUser();
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
if (user) {
return (
<div className="flex items-center space-x-4">
<img
src={user.picture || '/default-avatar.png'}
alt="Profile"
className="w-8 h-8 rounded-full"
/>
<span className="text-gray-700">Hello, {user.name}!</span>
<Link
href="/api/auth/logout"
className="bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-md text-sm font-medium"
>
Logout
</Link>
</div>
);
}
return (
<Link
href="/api/auth/login"
className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md text-sm font-medium"
>
Login
</Link>
);
}
Auth0 Authentication Handler Setup
// app/api/auth/[auth0]/route.ts - Auth0 Authentication Handler
import { handleAuth, handleLogin, handleCallback, handleLogout } from '@auth0/nextjs-auth0';
export const GET = handleAuth({
login: handleLogin({
authorizationParams: {
audience: process.env.AUTH0_AUDIENCE, // For API access
scope: 'openid profile email read:api', // Required scopes
},
}),
logout: handleLogout({
returnTo: '/' // Redirect destination after logout
}),
callback: handleCallback({
afterCallback: async (req, session, state) => {
// Custom callback processing
console.log('User logged in:', session.user);
return session;
},
}),
});
Authentication in Server Components
// app/profile/page.tsx - Profile Page (Server Component)
import { getSession } from '@auth0/nextjs-auth0';
import { redirect } from 'next/navigation';
export default async function ProfilePage() {
const session = await getSession();
if (!session) {
redirect('/api/auth/login');
}
const { user } = session;
return (
<div className="max-w-md mx-auto bg-white rounded-lg shadow-md p-6">
<div className="text-center">
<img
src={user?.picture || '/default-avatar.png'}
alt="Profile"
className="w-24 h-24 rounded-full mx-auto mb-4"
/>
<h1 className="text-2xl font-bold text-gray-900 mb-2">{user?.name}</h1>
<p className="text-gray-600 mb-4">{user?.email}</p>
<div className="bg-gray-50 rounded-lg p-4 mt-4">
<h2 className="text-lg font-semibold mb-2">User Information</h2>
<div className="text-left space-y-2">
<div>
<span className="font-medium">User ID:</span>
<span className="text-gray-600 ml-2">{user?.sub}</span>
</div>
<div>
<span className="font-medium">Last Updated:</span>
<span className="text-gray-600 ml-2">
{user?.updated_at && new Date(user.updated_at).toLocaleDateString('en-US')}
</span>
</div>
<div>
<span className="font-medium">Email Verified:</span>
<span className={`ml-2 ${user?.email_verified ? 'text-green-600' : 'text-red-600'}`}>
{user?.email_verified ? 'Verified' : 'Unverified'}
</span>
</div>
</div>
</div>
</div>
</div>
);
}
Protected Routes with Middleware
// middleware.ts - Next.js Middleware
import { withMiddlewareAuthRequired } from '@auth0/nextjs-auth0/edge';
export default withMiddlewareAuthRequired();
export const config = {
matcher: [
'/protected/:path*',
'/admin/:path*',
'/dashboard/:path*',
'/profile/:path*'
]
};
// Advanced Middleware configuration example
// middleware.ts - Custom Middleware
import { withMiddlewareAuthRequired, getSession } from '@auth0/nextjs-auth0/edge';
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export default withMiddlewareAuthRequired(async function middleware(req: NextRequest) {
const session = await getSession(req);
// Admin page access control
if (req.nextUrl.pathname.startsWith('/admin')) {
const userRoles = session?.user['https://your-app.com/roles'] || [];
if (!userRoles.includes('admin')) {
return NextResponse.redirect(new URL('/unauthorized', req.url));
}
}
// API protection
if (req.nextUrl.pathname.startsWith('/api/protected')) {
const accessToken = session?.accessToken;
if (!accessToken) {
return NextResponse.json(
{ error: 'Access token required' },
{ status: 401 }
);
}
}
return NextResponse.next();
});
Authentication in API Routes
// app/api/protected/users/route.ts - Protected API Route
import { getAccessToken, withApiAuthRequired } from '@auth0/nextjs-auth0';
import { NextRequest, NextResponse } from 'next/server';
export const GET = withApiAuthRequired(async function handler(req: NextRequest) {
try {
const { accessToken } = await getAccessToken(req);
// External API call
const response = await fetch('https://your-api.com/users', {
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error('External API request failed');
}
const users = await response.json();
return NextResponse.json({ users });
} catch (error) {
console.error('API error:', error);
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
});
export const POST = withApiAuthRequired(async function handler(req: NextRequest) {
try {
const { accessToken } = await getAccessToken(req);
const body = await req.json();
const response = await fetch('https://your-api.com/users', {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});
if (!response.ok) {
throw new Error('Failed to create user');
}
const newUser = await response.json();
return NextResponse.json({ user: newUser }, { status: 201 });
} catch (error) {
console.error('Create user error:', error);
return NextResponse.json(
{ error: 'Failed to create user' },
{ status: 500 }
);
}
});
Authentication State Management in Client Components
// components/UserDashboard.tsx - User Dashboard (Client Component)
'use client';
import { useUser } from '@auth0/nextjs-auth0/client';
import { useState, useEffect } from 'react';
interface UserStats {
loginCount: number;
lastLogin: string;
accountAge: number;
}
export default function UserDashboard() {
const { user, error, isLoading } = useUser();
const [stats, setStats] = useState<UserStats | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchUserStats() {
if (!user) return;
try {
const response = await fetch('/api/user/stats');
if (response.ok) {
const userStats = await response.json();
setStats(userStats);
}
} catch (error) {
console.error('Failed to fetch user stats:', error);
} finally {
setLoading(false);
}
}
fetchUserStats();
}, [user]);
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
if (!user) return <div>Login required</div>;
return (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<div className="bg-white rounded-lg shadow p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-2">Profile</h3>
<div className="flex items-center space-x-4">
<img
src={user.picture || '/default-avatar.png'}
alt="Profile"
className="w-16 h-16 rounded-full"
/>
<div>
<p className="font-medium">{user.name}</p>
<p className="text-gray-600">{user.email}</p>
</div>
</div>
</div>
<div className="bg-white rounded-lg shadow p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-2">Account Information</h3>
<div className="space-y-2">
<div>
<span className="text-gray-600">User ID:</span>
<p className="font-mono text-sm">{user.sub}</p>
</div>
<div>
<span className="text-gray-600">Email Verified:</span>
<p className={user.email_verified ? 'text-green-600' : 'text-red-600'}>
{user.email_verified ? 'Verified' : 'Unverified'}
</p>
</div>
</div>
</div>
{stats && (
<div className="bg-white rounded-lg shadow p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-2">Usage Statistics</h3>
<div className="space-y-2">
<div>
<span className="text-gray-600">Login Count:</span>
<p className="text-2xl font-bold">{stats.loginCount}</p>
</div>
<div>
<span className="text-gray-600">Last Login:</span>
<p>{new Date(stats.lastLogin).toLocaleDateString('en-US')}</p>
</div>
<div>
<span className="text-gray-600">Account Created:</span>
<p>{stats.accountAge} days ago</p>
</div>
</div>
</div>
)}
</div>
);
}
Custom Hooks and Context
// hooks/useAuthenticatedFetch.ts - Authenticated Fetch Hook
import { useUser } from '@auth0/nextjs-auth0/client';
import { useState, useCallback } from 'react';
interface FetchState<T> {
data: T | null;
loading: boolean;
error: string | null;
}
export function useAuthenticatedFetch<T>() {
const { user } = useUser();
const [state, setState] = useState<FetchState<T>>({
data: null,
loading: false,
error: null,
});
const fetchData = useCallback(async (url: string, options?: RequestInit) => {
if (!user) {
setState(prev => ({ ...prev, error: 'User not authenticated' }));
return;
}
setState(prev => ({ ...prev, loading: true, error: null }));
try {
const response = await fetch(url, {
...options,
headers: {
'Content-Type': 'application/json',
...options?.headers,
},
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
setState({ data, loading: false, error: null });
return data;
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'An error occurred';
setState(prev => ({ ...prev, loading: false, error: errorMessage }));
throw error;
}
}, [user]);
return { ...state, fetchData };
}
// components/DataLoader.tsx - Data Loader Component
'use client';
import { useAuthenticatedFetch } from '@/hooks/useAuthenticatedFetch';
import { useEffect } from 'react';
interface User {
id: string;
name: string;
email: string;
}
export default function DataLoader() {
const { data, loading, error, fetchData } = useAuthenticatedFetch<User[]>();
useEffect(() => {
fetchData('/api/protected/users');
}, [fetchData]);
if (loading) return <div>Loading data...</div>;
if (error) return <div>Error: {error}</div>;
if (!data) return <div>No data available</div>;
return (
<div className="space-y-4">
<h2 className="text-xl font-semibold">User List</h2>
<div className="grid gap-4">
{data.map((user) => (
<div key={user.id} className="bg-white p-4 rounded-lg shadow">
<h3 className="font-medium">{user.name}</h3>
<p className="text-gray-600">{user.email}</p>
</div>
))}
</div>
</div>
);
}
TypeScript Type Definitions and Customization
// types/auth0.ts - Auth0 Type Definition Extensions
import { Claims } from '@auth0/nextjs-auth0';
declare module '@auth0/nextjs-auth0' {
interface UserProfile extends Claims {
'https://your-app.com/roles'?: string[];
'https://your-app.com/permissions'?: string[];
user_metadata?: {
preferences?: {
theme?: 'light' | 'dark';
language?: 'ja' | 'en';
notifications?: boolean;
};
};
app_metadata?: {
plan?: 'free' | 'pro' | 'enterprise';
subscription_id?: string;
};
}
}
// hooks/useUserRoles.ts - User Role Management Hook
import { useUser } from '@auth0/nextjs-auth0/client';
export function useUserRoles() {
const { user } = useUser();
const roles = user?.['https://your-app.com/roles'] || [];
const permissions = user?.['https://your-app.com/permissions'] || [];
const hasRole = (role: string) => roles.includes(role);
const hasPermission = (permission: string) => permissions.includes(permission);
const isAdmin = hasRole('admin');
const isModerator = hasRole('moderator') || isAdmin;
return {
roles,
permissions,
hasRole,
hasPermission,
isAdmin,
isModerator,
};
}
// components/RoleBasedComponent.tsx - Role-Based Component
'use client';
import { useUserRoles } from '@/hooks/useUserRoles';
import { ReactNode } from 'react';
interface RoleBasedComponentProps {
allowedRoles: string[];
children: ReactNode;
fallback?: ReactNode;
}
export default function RoleBasedComponent({
allowedRoles,
children,
fallback = null
}: RoleBasedComponentProps) {
const { hasRole } = useUserRoles();
const hasAccess = allowedRoles.some(role => hasRole(role));
if (!hasAccess) {
return <>{fallback}</>;
}
return <>{children}</>;
}
// Usage example
// <RoleBasedComponent allowedRoles={['admin', 'moderator']}>
// <AdminPanel />
// </RoleBasedComponent>