Skip to main content

Use AI to integrate Auth0

If you use an AI coding assistant like Claude Code, Cursor, or GitHub Copilot, you can add Auth0 authentication automatically in minutes using agent skills.Install:
npx skills add auth0/agent-skills
Then ask your AI assistant:
Add Auth0 authentication to my Next.js app
Your AI assistant will automatically create your Auth0 application, fetch credentials, install @auth0/nextjs-auth0, create API routes, and set up environment variables. Full agent skills documentation →
Prerequisites: Before you begin, ensure you have the following installed:Next.js Version Compatibility: This quickstart uses Next.js 15 which is fully supported by the Auth0 SDK. Next.js 16 is also compatible but requires the --legacy-peer-deps flag during installation (see Step 2 for details).

Get Started

This quickstart demonstrates how to add Auth0 authentication to a Next.js application. You’ll build a full-stack web application with server-side rendering, secure login functionality, and protected routes using the Auth0 Next.js SDK.
1

Create a new project

Create a new Next.js project for this Quickstart
npx create-next-app@15 auth0-nextjs --typescript --tailwind --eslint --app --src-dir --import-alias "@/*" --yes
Open the project
cd auth0-nextjs
We’re using create-next-app@15 to create a Next.js 15 project, which is fully supported by the Auth0 SDK. If you prefer to use Next.js 16 or already have a Next.js 16 project, you’ll need to use --legacy-peer-deps when installing the Auth0 SDK in Step 2.
2

Install the Auth0 Next.js SDK

npm install @auth0/nextjs-auth0
For Next.js 16 users: If you’re using Next.js 16 (or upgraded to it), install with the --legacy-peer-deps flag:
npm install @auth0/nextjs-auth0 --legacy-peer-deps
The --legacy-peer-deps flag is needed because Next.js 16 support is pending in the SDK. The SDK works correctly with Next.js 16 using this flag.
3

Create project files

Create all necessary directories and files for Auth0 integration:
mkdir -p src/lib src/components && touch src/lib/auth0.ts src/middleware.ts src/components/LoginButton.tsx src/components/LogoutButton.tsx src/components/Profile.tsx
4

Setup your Auth0 App

Next up, you need to create a new app on your Auth0 tenant and add the environment variables to your project.You have three options to set up your Auth0 app: use the Quick Setup tool (recommended), run a CLI command, or configure manually via the Dashboard:
5

Create the Auth0 configuration

Add the Auth0 client code to src/lib/auth0.ts:
src/lib/auth0.ts
import { Auth0Client } from '@auth0/nextjs-auth0/server';

export const auth0 = new Auth0Client();
6

Add middleware

Add the middleware code to src/middleware.ts:
src/middleware.ts
import type { NextRequest } from "next/server";
import { auth0 } from "./lib/auth0";

export async function middleware(request: NextRequest) {
  return await auth0.middleware(request);
}

export const config = {
  matcher: [
    /*
     * Match all request paths except for the ones starting with:
     * - _next/static (static files)
     * - _next/image (image optimization files)
     * - favicon.ico, sitemap.xml, robots.txt (metadata files)
     */
    "/((?!_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)",
  ],
};
Since we’re using a src/ directory, the middleware.ts file is created inside src/. If you’re not using a src/ directory, create it in the project root instead.
This middleware automatically mounts the following authentication routes:
  • /auth/login - Login route
  • /auth/logout - Logout route
  • /auth/callback - Callback route
  • /auth/profile - User profile route
  • /auth/access-token - Access token route
  • /auth/backchannel-logout - Backchannel logout route
7

Create Login, Logout and Profile Components

Add the component code to the files created in Step 3:
8

Update your main page

Replace src/app/page.tsx with:
src/app/page.tsx
import { auth0 } from "@/lib/auth0";
import LoginButton from "@/components/LoginButton";
import LogoutButton from "@/components/LogoutButton";
import Profile from "@/components/Profile";

export default async function Home() {
  const session = await auth0.getSession();
  const user = session?.user;

  return (
    <div className="app-container">
      <div className="main-card-wrapper">
        <img
          src="https://cdn.auth0.com/quantum-assets/dist/latest/logos/auth0/auth0-lockup-en-ondark.png"
          alt="Auth0 Logo"
          className="auth0-logo"
        />
        <h1 className="main-title">Next.js + Auth0</h1>
        
        <div className="action-card">
          {user ? (
            <div className="logged-in-section">
              <p className="logged-in-message">Successfully logged in!</p>
              <Profile />
              <LogoutButton />
            </div>
          ) : (
            <>
              <p className="action-text">
                Welcome! Please log in to access your protected content.
              </p>
              <LoginButton />
            </>
          )}
        </div>
      </div>
    </div>
  );
}
9

Update layout with Auth0Provider (OPTIONAL)

Update src/app/layout.tsx to wrap your app with the Auth0Provider:
src/app/layout.tsx
import type { Metadata } from "next";
import { Auth0Provider } from "@auth0/nextjs-auth0/client";
import "./globals.css";

export const metadata: Metadata = {
  title: "Auth0 Next.js App",
  description: "Next.js app with Auth0 authentication",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <Auth0Provider>
          {children}
        </Auth0Provider>
      </body>
    </html>
  );
}
In v4, the Auth0Provider is optional. You only need it if you want to pass an initial user during server rendering to be available to the useUser() hook.
10

Add styling

Replace src/app/globals.css with modern Auth0-branded styling:
src/app/globals.css
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
@tailwind base;
@tailwind components;
@tailwind utilities;

body {
  margin: 0;
  font-family: 'Inter', sans-serif;
  background-color: #1a1e27;
  min-height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  color: #e2e8f0;
  overflow: hidden;
}

.app-container {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
  width: 100%;
  padding: 1rem;
  box-sizing: border-box;
}

.loading-state, .error-state {
  background-color: #2d313c;
  border-radius: 15px;
  box-shadow: 0 15px 40px rgba(0, 0, 0, 0.4);
  padding: 3rem;
  text-align: center;
}

.loading-text {
  font-size: 1.8rem;
  font-weight: 500;
  color: #a0aec0;
  animation: pulse 1.5s infinite ease-in-out;
}

.main-card-wrapper {
  background-color: #262a33;
  border-radius: 20px;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(255, 255, 255, 0.05);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2rem;
  padding: 3rem;
  max-width: 500px;
  width: 90%;
  animation: fadeInScale 0.8s ease-out forwards;
}

.auth0-logo {
  width: 160px;
  margin-bottom: 1.5rem;
  opacity: 0;
  animation: slideInDown 1s ease-out forwards 0.2s;
}

.main-title {
  font-size: 2.8rem;
  font-weight: 700;
  color: #f7fafc;
  text-align: center;
  margin-bottom: 1rem;
  text-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
  opacity: 0;
  animation: fadeIn 1s ease-out forwards 0.4s;
}

.action-card {
  background-color: #2d313c;
  border-radius: 15px;
  box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.3), 0 5px 15px rgba(0, 0, 0, 0.3);
  padding: 2.5rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.8rem;
  width: calc(100% - 2rem);
  opacity: 0;
  animation: fadeIn 1s ease-out forwards 0.6s;
}

.action-text {
  font-size: 1.25rem;
  color: #cbd5e0;
  text-align: center;
  line-height: 1.6;
  font-weight: 400;
}

.button {
  padding: 1.1rem 2.8rem;
  font-size: 1.2rem;
  font-weight: 600;
  border-radius: 10px;
  border: none;
  cursor: pointer;
  transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.4);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  outline: none;
}

.button:focus {
  box-shadow: 0 0 0 4px rgba(99, 179, 237, 0.5);
}

.button.login {
  background-color: #63b3ed;
  color: #1a1e27;
}

.button.login:hover {
  background-color: #4299e1;
  transform: translateY(-5px) scale(1.03);
  box-shadow: 0 12px 25px rgba(0, 0, 0, 0.5);
}

.button.logout {
  background-color: #fc8181;
  color: #1a1e27;
}

.button.logout:hover {
  background-color: #e53e3e;
  transform: translateY(-5px) scale(1.03);
  box-shadow: 0 12px 25px rgba(0, 0, 0, 0.5);
}

.logged-in-section {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.5rem;
  width: 100%;
}

.logged-in-message {
  font-size: 1.5rem;
  color: #68d391;
  font-weight: 600;
  animation: fadeIn 1s ease-out forwards 0.8s;
}

.profile-card {
  padding: 2.2rem;
  animation: scaleIn 0.8s ease-out forwards 1.2s;
}

.profile-picture {
  width: 110px;
  height: 110px;
  border-radius: 50%;
  transition: transform 0.3s ease-in-out;
  object-fit: cover;
}

.profile-picture:hover {
  transform: scale(1.05);
}

.profile-name {
  font-size: 2rem;
  margin-top: 0.5rem;
}

.profile-email {
  font-size: 1.15rem;
  text-align: center;
}

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes fadeInScale {
  from { opacity: 0; transform: scale(0.95); }
  to { opacity: 1; transform: scale(1); }
}

@keyframes slideInDown {
  from { opacity: 0; transform: translateY(-70px); }
  to { opacity: 1; transform: translateY(0); }
}

@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.6; }
}

@keyframes scaleIn {
  from { opacity: 0; transform: scale(0.8); }
  to { opacity: 1; transform: scale(1); }
}

@media (max-width: 600px) {
  .main-card-wrapper {
    padding: 2rem;
    gap: 1.5rem;
  }
  
  .main-title {
    font-size: 2.2rem;
  }
  
  .button {
    padding: 0.9rem 2rem;
    font-size: 1rem;
  }
  
  .auth0-logo {
    width: 120px;
  }
}
11

Run your app

npm run dev
Your app will be available at http://localhost:3000. The Auth0 SDK v4 automatically mounts authentication routes at /auth/* (not /api/auth/* like in v3).
CheckpointYou should now have a fully functional Auth0 login page running on your localhost

Troubleshooting

If you see a JWEDecryptionFailed: decryption operation failed error, this is caused by either an invalid AUTH0_SECRET or an old session cookie encrypted with a different secret.Solution:
  1. Generate a new secret using:
openssl rand -hex 32
  1. Update your .env.local file:
AUTH0_SECRET=<your-new-64-character-hex-string>
  1. Clear your browser cookies for localhost:3000:
    • Chrome/Edge: Press F12 → Application tab → Cookies → Delete all cookies for localhost
    • Firefox: Press F12 → Storage tab → Cookies → Delete all cookies for localhost
    • Safari: Develop menu → Show Web Inspector → Storage tab → Cookies → Delete all
  2. Restart your dev server:
npm run dev
The secret must be exactly 32 bytes (64 hexadecimal characters). The error occurs when the app tries to decrypt an existing session cookie that was encrypted with a different secret.
If clicking login takes you to a 404 page, check these common issues:
  1. Middleware location: Ensure src/middleware.ts exists in the correct location
  2. Middleware code: Verify the middleware matches the code in Step 6
  3. Restart server: After creating middleware, restart the dev server
  4. Check imports: Make sure import { auth0 } from "./lib/auth0" path is correct
If you see “Cannot find module ’@/components/LoginButton’” or similar errors:
  1. Verify files exist: Check that all files from Step 3 were created
  2. Check paths: Ensure components are in src/components/ directory
  3. Restart TypeScript: Press Cmd+Shift+P (Mac) or Ctrl+Shift+P (Windows) and run “TypeScript: Restart TS Server”
  4. Verify imports: Make sure you’re using @/components/* (not ~/components/*)

Advanced Usage

This quickstart uses Auth0 Next.js SDK v4, which has significant changes from v3:
  • No dynamic route handlers needed - Authentication routes are auto-mounted by middleware
  • Simplified client setup - new Auth0Client() reads environment variables automatically
  • New route paths - Routes are at /auth/* instead of /api/auth/*
  • Required middleware - All authentication functionality goes through middleware
  • Use <a> tags - Navigation must use <a href="/auth/login"> instead of buttons with onClick

Authentication Routes

The SDK automatically mounts these routes via middleware:
RoutePurpose
/auth/loginInitiate login
/auth/logoutLogout user
/auth/callbackHandle Auth0 callback
/auth/profileGet user profile
/auth/access-tokenGet access token
/auth/backchannel-logoutHandle backchannel logout
If you’re experiencing 404 errors on these routes, ensure that:
  1. The middleware.ts file is created in the correct location (project root, or inside src/ if using a src/ directory)
  2. The middleware is properly configured with the matcher pattern shown in Step 5
  3. The development server was restarted after creating the middleware file
The Auth0 Next.js SDK v4 supports both App Router and Pages Router patterns. Here are some common server-side patterns:
app/protected/page.tsx
import { auth0 } from "@/lib/auth0";
import { redirect } from "next/navigation";

export default async function ProtectedPage() {
  const session = await auth0.getSession();
  
  if (!session) {
    redirect('/auth/login');
  }

  return (
    <div>
      <h1>Protected Content</h1>
      <p>Welcome, {session.user.name}!</p>
    </div>
  );
}
For client-side authentication state, use the useUser hook:
components/UserProfile.tsx
"use client";

import { useUser } from "@auth0/nextjs-auth0";

export default function UserProfile() {
  const { user, error, isLoading } = useUser();

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  if (!user) return <div>Not logged in</div>;

  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
      <img src={user.picture} alt="Profile" />
    </div>
  );
}
For API route protection, use the withApiAuthRequired method:
app/api/protected/route.ts
import { auth0 } from "@/lib/auth0";

export const GET = auth0.withApiAuthRequired(async function handler() {
  const session = await auth0.getSession();
  
  return Response.json({
    message: "This is a protected API route",
    user: session?.user
  });
});