Auth0とNext.jsで多要素認証を実装し、認証の追加要求や要素の登録・確認、リダイレクトやポップアップによる再認証を扱います。
- 重要な操作の前に追加認証したいとき
- MFAの登録や検証画面を作りたいとき
- 認証不足時にログインへ誘導したいとき
Next.jsアプリにAuth0認証を導入し、ログイン設定からセッション取得、ページやAPIの保護までをApp Router・Pages Router別に案内します。
Add authentication to Next.js applications using @auth0/nextjs-auth0 v4 (App Router + Pages Router): install, Auth0Client singleton, middleware/proxy route mounting, env config, useUser, getSession/getAccessToken, protecting pages and API routes, and step-up MFA. Entry point for all nextjs-auth0 skills.
実行スコア 未検証
インストール方法を見る@auth0/nextjs-auth0 v4を使って、Next.jsアプリにAuth0認証を導入する手順を案内します。App RouterとPages Routerの両方を対象に、Auth0Clientの作成、環境変数の設定、middleware.tsまたはproxy.tsによる認証ルートの設置、ログイン画面やユーザー情報の取得方法を扱います。さらに、サーバーコンポーネントやAPIルートでセッションを確認してページを保護する方法、外部API呼び出し用のアクセストークン取得、MFAへの導線も説明します。
Next.jsのWebアプリにログイン機能を加えたい場合や、ログイン必須のページ・APIを作りたい場合に向いています。Auth0の設定を始める人が、必要な環境変数やコールバックURL、ルート構成を確認するときにも使えます。
@auth0/nextjs-auth0 v4とNext.js 14.2.35以上が対象です。Auth0アカウントとアプリの設定が必要です。クライアントのみのReactアプリ、React Native、Next.js以外のフレームワーク、セッション不要のAPIだけの構成は対象外です。
この紹介文は、公開されている SKILL.md をもとに AI(OpenAI Codex)が作成しました。正確な仕様は下の原文を確認してください。
インストールする前に、エージェントに与えられる指示の中身を確認できます。
Add authentication to Next.js applications using @auth0/nextjs-auth0. Supports both App Router and Pages Router.
| Task | Sub-skill |
|---|---|
Step-up auth, mfa_required, enroll/challenge/verify, popup or redirect step-up | mfa/SKILL.md |
Multi-tenant B2B, pass organization at login, read org_id, invitations, org/connection setup | organizations/SKILL.md |
Passkey signup, sign-in, and My Account enrollment using the server-side auth0.passkey.* API | passkeys/SKILL.md |
auth0 login, then auth0 apps create)npm install @auth0/nextjs-auth0
For automated setup with Auth0 CLI, see the Setup Guide section below for complete scripts.
For manual setup:
Create .env.local:
AUTH0_SECRET=<generate-a-32-character-secret>
APP_BASE_URL=http://localhost:3000
AUTH0_DOMAIN=your-tenant.auth0.com
AUTH0_CLIENT_ID=your-client-id
AUTH0_CLIENT_SECRET=your-client-secret
Generate secret: openssl rand -hex 32
Important: Add .env.local to .gitignore
Detect project structure first: Check whether the project uses a src/ directory (i.e. src/app/ or src/pages/ exists). This determines where to place files:
src/: src/lib/auth0.ts, src/middleware.ts (or src/proxy.ts for Next.js 16)src/: lib/auth0.ts, middleware.ts (or proxy.ts for Next.js 16)Create lib/auth0.ts (or src/lib/auth0.ts if using the src/ convention):
import { Auth0Client } from '@auth0/nextjs-auth0/server';
export const auth0 = new Auth0Client({
domain: process.env.AUTH0_DOMAIN!,
clientId: process.env.AUTH0_CLIENT_ID!,
clientSecret: process.env.AUTH0_CLIENT_SECRET!,
secret: process.env.AUTH0_SECRET!,
appBaseUrl: process.env.APP_BASE_URL!,
});
Middleware Configuration (Next.js 15 vs 16):
Next.js 15 - Create middleware.ts (at project root, or src/middleware.ts if using src/):
import { NextRequest } from 'next/server';
import { auth0 } from '@/lib/auth0';
export async function middleware(request: NextRequest) {
return await auth0.middleware(request);
}
export const config = {
matcher: [
'/((?!_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
],
};
Next.js 16 - You have two options:
Option 1: Use middleware.ts (same as Next.js 15, same src/ placement rules).
Option 2: Use proxy.ts (at project root, or src/proxy.ts if using src/):
import { NextRequest } from 'next/server';
import { auth0 } from '@/lib/auth0';
export async function proxy(request: NextRequest) {
return await auth0.middleware(request);
}
export const config = {
matcher: [
'/((?!_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
],
};
This automatically creates endpoints:
/auth/login - Login/auth/logout - Logout/auth/callback - OAuth callback/auth/profile - User profileNote: In v4, wrapping with <Auth0Provider> is optional. Only needed if you want to pass an initial user during server rendering to useUser().
App Router - Optionally wrap app in app/layout.tsx:
import { Auth0Provider } from '@auth0/nextjs-auth0/client';
import { auth0 } from '@/lib/auth0';
export default async function RootLayout({ children }: { children: React.ReactNode }) {
const session = await auth0.getSession();
return (
<html>
<body>
<Auth0Provider user={session?.user}>{children}</Auth0Provider>
</body>
</html>
);
}
Pages Router - Optionally wrap app in pages/_app.tsx:
import { Auth0Provider } from '@auth0/nextjs-auth0/client';
import type { AppProps } from 'next/app';
export default function App({ Component, pageProps }: AppProps) {
return (
<Auth0Provider user={pageProps.user}>
<Component {...pageProps} />
</Auth0Provider>
);
}
Client Component (works in both routers):
'use client'; // Only needed for App Router
import { useUser } from '@auth0/nextjs-auth0/client';
export default function Profile() {
const { user, isLoading } = useUser();
if (isLoading) return <div>Loading...</div>;
if (user) {
return (
<div>
<img src={user.picture} alt={user.name} />
<h2>Welcome, {user.name}!</h2>
<a href="/auth/logout">Logout</a>
</div>
);
}
return <a href="/auth/login">Login</a>;
}
Start your dev server:
npm run dev
Visit http://localhost:3000 and test the login flow.
| Mistake | Fix |
|---|---|
| Using v3 environment variables | v4 uses APP_BASE_URL and AUTH0_DOMAIN (not AUTH0_BASE_URL or AUTH0_ISSUER_BASE_URL) |
| Forgot to add callback URL in Auth0 Dashboard | Add /auth/callback to Allowed Callback URLs (e.g., http://localhost:3000/auth/callback) |
| Missing middleware configuration | v4 requires middleware to mount auth routes - create middleware.ts (Next.js 15+16) or proxy.ts (Next.js 16 only) with auth0.middleware() |
| Wrong route paths | v4 uses /auth/login not /api/auth/login - routes drop the /api prefix |
| Missing or weak AUTH0_SECRET | Generate secure secret with openssl rand -hex 32 and store in .env.local |
| Using .env instead of .env.local | Next.js requires .env.local for local secrets, and .env.local should be in .gitignore |
| App created as SPA type in Auth0 | Must be Regular Web Application type for Next.js |
Using standalone withPageAuthRequired / withApiAuthRequired | Use the instance methods auth0.withPageAuthRequired() and auth0.withApiAuthRequired(), or auth0.getSession() directly |
| Using useUser in Server Component | useUser is client-only, use auth0.getSession() for Server Components |
| AUTH0_DOMAIN includes https:// | v4 AUTH0_DOMAIN should be just the domain (e.g., example.auth0.com), no scheme |
auth0 login, then auth0 apps create)V4 Setup:
src/ convention: check if src/app/ or src/pages/ exists — place all files inside src/ if solib/auth0.ts (or src/lib/auth0.ts) with Auth0Client instancemiddleware.ts (or src/middleware.ts) with middleware() functionmiddleware.ts with middleware() OR proxy.ts with proxy() function (same src/ rules)<Auth0Provider> for SSR userClient-Side Hooks:
useUser() - Get user in client componentsuser - User profile objectisLoading - Loading stateServer-Side Methods:
auth0.getSession() - Get session in Server Components/API routes/middlewareauth0.getAccessToken() - Get access token for calling APIs<a href="/auth/login?returnTo=/dashboard">
Login and go to Dashboard
</a>
Or programmatically:
const router = useRouter();
router.push('/auth/login?returnTo=/dashboard');
import { auth0 } from '@/lib/auth0';
import { NextResponse } from 'next/server';
export async function GET() {
const { token } = await auth0.getAccessToken();
if (!token) {
return new NextResponse('Unauthorized', { status: 401 });
}
const apiResponse = await fetch('https://api.example.com/data', {
headers: {
Authorization: `Bearer ${token}`
}
});
return NextResponse.json(await apiResponse.json());
}
Users remain logged in across sessions automatically with refresh tokens.
To force re-authentication:
<a href="/auth/login?prompt=login">
Force Re-login
</a>
import { Auth0Client } from '@auth0/nextjs-auth0/server';
export const auth0 = new Auth0Client({
domain: process.env.AUTH0_DOMAIN!,
clientId: process.env.AUTH0_CLIENT_ID!,
clientSecret: process.env.AUTH0_CLIENT_SECRET!,
secret: process.env.AUTH0_SECRET!,
appBaseUrl: process.env.APP_BASE_URL!,
authorizationParameters: {
scope: 'openid profile email',
audience: process.env.AUTH0_AUDIENCE,
},
routes: {
login: '/auth/login',
callback: '/auth/callback',
logout: '/auth/logout',
profile: '/auth/profile',
},
session: {
rolling: true,
inactivityDuration: 24 * 60 * 60, // 24 hours in seconds
absoluteDuration: 7 * 24 * 60 * 60, // 7 days in seconds
},
});
Note: Most configuration can be omitted - v4 uses sensible defaults. The middleware automatically mounts auth routes.
Server-side and client-side auth patterns for both App and Pages Router.
App Router - Server Component
// app/profile/page.tsx
import { auth0 } from '@/lib/auth0';
import { redirect } from 'next/navigation';
export default async function Profile() {
// In App Router, getSession() reads the request/response from Next.js' async context,
// so you don't pass req/res like in the Pages Router example below.
const session = await auth0.getSession();
if (!session) {
redirect('/auth/login?returnTo=/profile');
}
return (
<div>
<h1>Welcome, {session.user.name}!</h1>
<img src={session.user.picture} alt={session.user.name} />
</div>
);
}
Pages Router - SSR
// pages/profile.tsx
import { auth0 } from '@/lib/auth0';
import { GetServerSideProps } from 'next';
export default function Profile({ user }: { user: any }) {
return <h1>Welcome, {user.name}!</h1>;
}
export const getServerSideProps: GetServerSideProps = async ({ req }) => {
const session = await auth0.getSession(req);
if (!session) {
return {
redirect: {
destination: '/auth/login?returnTo=/profile',
permanent: false,
},
};
}
return {
props: { user: session.user },
};
};
App Router
// app/api/private/route.ts
import { auth0 } from '@/lib/auth0';
import { NextResponse } from 'next/server';
export async function GET(request: Request) {
const session = await auth0.getSession();
if (!session) {
return new NextResponse('Unauthorized', { status: 401 });
}
return NextResponse.json({ data: 'Protected data', user: session.user });
}
Pages Router
// pages/api/private.ts
import { auth0 } from '@/lib/auth0';
import { NextApiRequest, NextApiResponse } from 'next';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const session = await auth0.getSession(req);
if (!session) {
return res.status(401).json({ error: 'Unauthorized' });
}
res.json({ user: session.user });
}
Protect multiple routes with middleware.
File placement: If the project uses a src/ directory (i.e. src/app/ exists), place middleware.ts or proxy.ts inside src/. Otherwise, place at the project root.
Next.js 15 - Use middleware.ts (or src/middleware.ts):
// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
import { auth0 } from '@/lib/auth0';
export async function middleware(request: NextRequest) {
const authRes = await auth0.middleware(request);
// Allow auth routes to be handled by SDK
if (request.nextUrl.pathname.startsWith('/auth')) {
return authRes;
}
// Public routes
if (request.nextUrl.pathname === '/') {
return authRes;
}
// Protected routes - check session
const session = await auth0.getSession(request);
if (!session) {
const { origin } = new URL(request.url);
return NextResponse.redirect(`${origin}/auth/login?returnTo=${request.nextUrl.pathname}`);
}
return authRes;
}
export const config = {
matcher: [
'/((?!_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
],
};
Next.js 16 - Use either middleware.ts (same as above) or proxy.ts with a proxy() export (same src/ placement rules).
App Router - Server Action
// app/actions.ts
'use server';
import { auth0 } from '@/lib/auth0';
export async function getData() {
const { token } = await auth0.getAccessToken();
if (!token) {
throw new Error('No access token available');
}
const response = await fetch('https://api.example.com/data', {
headers: { Authorization: `Bearer ${token}` }
});
return response.json();
}
Pages Router - API Route
// pages/api/data.ts
import { auth0 } from '@/lib/auth0';
import { NextApiRequest, NextApiResponse } from 'next';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const session = await auth0.getSession(req);
if (!session) {
return res.status(401).json({ error: 'Unauthorized' });
}
const { token } = await auth0.getAccessToken(req, res);
if (!token) {
return res.status(401).json({ error: 'No access token' });
}
const response = await fetch('https://api.example.com/data', {
headers: { Authorization: `Bearer ${token}` }
});
const data = await response.json();
res.json(data);
}
npm run devhttp://localhost:3000| Issue | Solution |
|---|---|
| "Missing required parameter: redirect_uri" | Ensure APP_BASE_URL is set correctly (v4 renamed from AUTH0_BASE_URL) |
| "Invalid state" error | Clear cookies/storage. Verify callback URL in Auth0 dashboard matches APP_BASE_URL/auth/callback |
| User session not persisting | Check AUTH0_SECRET is set and at least 32 characters |
| API routes return 401 | Check session with auth0.getSession() in route handler |
| Middleware loops infinitely | Do not exclude /auth/* from the matcher — those routes must reach auth0.middleware(). Instead return authRes early when request.nextUrl.pathname.startsWith('/auth') |
Import errors for withApiAuthRequired / withPageAuthRequired | These are instance methods — use auth0.withApiAuthRequired() / auth0.withPageAuthRequired(), or auth0.getSession() directly |
| Environment variable not recognized | v4 uses AUTH0_DOMAIN (no scheme) and APP_BASE_URL, not AUTH0_ISSUER_BASE_URL or AUTH0_BASE_URL |
| Callback URL mismatch | Add /auth/callback to Allowed Callback URLs (v4 dropped /api prefix) |
| Routes return 404 | v4 uses /auth/* paths, not /api/auth/* - update all auth links |
.env.local to version controlAUTH0_SECRET periodicallyNever read the contents of .env.local or .env at any point during setup. The file may contain sensitive secrets that should not be exposed in the LLM context. If you determine you need to read the file for any reason, ask the user for explicit permission before doing so — do not proceed until the user confirms.
Before running any part of this setup that writes to an env file, you MUST ask the user for explicit confirmation. Follow the steps below precisely.
Before writing credentials, check which env files exist:
test -f .env.local && echo "ENV_LOCAL_EXISTS" || echo "ENV_LOCAL_NOT_FOUND"
test -f .env && echo "ENV_EXISTS" || echo "ENV_NOT_FOUND"
Then ask the user for explicit confirmation before proceeding — do not continue until the user confirms:
.env.local exists, ask whether to append Auth0 credentials to it (it may contain unrelated secrets; the setup appends without modifying existing content)..env.local does not exist but .env exists, ask the same about .env..env.local with Auth0 credentials and a placeholder for AUTH0_CLIENT_SECRET.Do not proceed with writing to any env file unless the user selects the confirmation option.
#!/bin/bash
# Install Auth0 CLI
if ! command -v auth0 &> /dev/null; then
if [[ "$OSTYPE" == "darwin"* ]]; then
brew install auth0
else
INSTALLER=$(mktemp)
curl -sSfL https://raw.githubusercontent.com/auth0/auth0-cli/main/install.sh -o "$INSTALLER"
mkdir -p "$HOME/.local/bin" && sh "$INSTALLER" -b "$HOME/.local/bin"
export PATH="$HOME/.local/bin:$PATH"
rm -f "$INSTALLER"
fi
fi
# Require jq for JSON parsing
if ! command -v jq &> /dev/null; then
echo "Error: jq is required. Install it first (e.g. apt install jq / brew install jq)." >&2
exit 1
fi
# Login
if ! auth0 tenants list &> /dev/null; then
echo "Visit https://auth0.com/signup if you need an account"
auth0 login
fi
# Create/select app
auth0 apps list
read -p "Enter app ID (or Enter to create new): " APP_ID
if [ -z "$APP_ID" ]; then
APP_ID=$(auth0 apps create \
--name "${PWD##*/}-nextjs" \
--type regular \
--callbacks "http://localhost:3000/auth/callback" \
--logout-urls "http://localhost:3000" \
--metadata "created_by=agent_skills" \
--json | jq -r '.client_id')
fi
# Get credentials
APP_JSON=$(auth0 apps show "$APP_ID" --json)
AUTH0_DOMAIN=$(echo "$APP_JSON" | jq -r '.domain // empty')
AUTH0_CLIENT_ID=$(echo "$APP_JSON" | jq -r '.client_id // empty')
if [ -z "$AUTH0_DOMAIN" ] || [ -z "$AUTH0_CLIENT_ID" ]; then
echo "Error: failed to retrieve app credentials. Check the app ID and try again." >&2
exit 1
fi
# Generate secret
AUTH0_SECRET=$(openssl rand -hex 32)
# Determine target env file
if [ -f .env.local ]; then
TARGET_FILE=".env.local"
elif [ -f .env ]; then
TARGET_FILE=".env"
else
TARGET_FILE=".env.local"
fi
# Append Auth0 credentials
cat >> "$TARGET_FILE" << ENVEOF
AUTH0_SECRET=$AUTH0_SECRET
APP_BASE_URL=http://localhost:3000
AUTH0_DOMAIN=$AUTH0_DOMAIN
AUTH0_CLIENT_ID=$AUTH0_CLIENT_ID
AUTH0_CLIENT_SECRET='YOUR_CLIENT_SECRET'
ENVEOF
echo "✅ Auth0 credentials written to $TARGET_FILE"
After the script runs, remind the user to:
YOUR_CLIENT_SECRET with the actual client secret from Auth0..gitignore to avoid accidentally committing secrets.Install the SDK (npm install @auth0/nextjs-auth0), create .env.local with the five variables above (generate AUTH0_SECRET with openssl rand -hex 32), then configure the Auth0 Application as a Regular Web Application with Allowed Callback URL http://localhost:3000/auth/callback and Allowed Logout URL http://localhost:3000 — via the CLI (auth0 login, auth0 apps create --type regular ...) or the dashboard.
Targets @auth0/nextjs-auth0 v4.30.0. See the V*_MIGRATION_GUIDE.md files for older APIs. The MFA management API is Early Access.
まだレビューはありません。使ってみた感想をお寄せください。
ログインなしで書き込めます。使ってみた感想、質問、動かなかった環境などを気軽にどうぞ。
まだコメントはありません。最初のコメントを書いてみませんか。
Auth0とNext.jsで多要素認証を実装し、認証の追加要求や要素の登録・確認、リダイレクトやポップアップによる再認証を扱います。
顧客企業ごとにログイン先やメンバー権限を分ける、Auth0 Organizationsを使ったB2B向けマルチテナント認証の実装を支援します。
パスキーの登録・ログイン・アカウントへの追加を、Auth0のサーバーAPIとブラウザーのWebAuthn処理を組み合わせて実装します。