nextauth-authentication
Next.jsアプリケーションにおけるNextAuth.js(Auth.js v5)の認証実装に関するガイドラインで、セッション管理とセキュリティのベストプラクティスを網羅しています。ログイン・ログアウトやセッション制御の実装時に活用でき、安全な認証フローの構築をサポートします。
description の原文を見る
Guidelines for implementing NextAuth.js (Auth.js v5) authentication in Next.js applications with session management and security best practices
SKILL.md 本文
NextAuth認証
NextAuth.js (Auth.js v5) 認証実装のエキスパートです。Next.js アプリケーションに認証を統合する際は、以下のガイドラインに従ってください。
コアプリンシパル
- Auth.js v5 パターンとユニバーサルな
auth()関数を使用する - ニーズに基づいて適切なセッション管理戦略を実装する
- センシティブな操作ではサーバー側でセッションを常に検証する
AUTH_プレフィックスを使用して環境変数を正しく設定する
インストール
npm install next-auth@beta
環境変数
# 必須
AUTH_SECRET=your-32-byte-secret-here # Generate with: openssl rand -base64 32
# プロバイダー認証情報 (正しい名前で自動検出)
AUTH_GITHUB_ID=your-github-client-id
AUTH_GITHUB_SECRET=your-github-client-secret
AUTH_GOOGLE_ID=your-google-client-id
AUTH_GOOGLE_SECRET=your-google-client-secret
# オプション: カスタム URL (ほとんどの環境で自動検出)
AUTH_URL=https://your-domain.com
基本設定
auth.ts (ルート設定)
import NextAuth from 'next-auth';
import GitHub from 'next-auth/providers/github';
import Google from 'next-auth/providers/google';
import Credentials from 'next-auth/providers/credentials';
import { PrismaAdapter } from '@auth/prisma-adapter';
import { prisma } from '@/lib/prisma';
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: PrismaAdapter(prisma),
providers: [
GitHub, // Credentials auto-detected from AUTH_GITHUB_ID/SECRET
Google,
Credentials({
credentials: {
email: { label: 'Email', type: 'email' },
password: { label: 'Password', type: 'password' },
},
async authorize(credentials) {
// Validate credentials
const user = await validateCredentials(credentials);
if (!user) return null;
return user;
},
}),
],
session: {
strategy: 'jwt', // or 'database'
maxAge: 30 * 24 * 60 * 60, // 30 days
},
pages: {
signIn: '/auth/signin',
error: '/auth/error',
},
callbacks: {
async jwt({ token, user, account }) {
if (user) {
token.id = user.id;
token.role = user.role;
}
return token;
},
async session({ session, token }) {
if (token) {
session.user.id = token.id as string;
session.user.role = token.role as string;
}
return session;
},
async authorized({ auth, request }) {
const isAuthenticated = !!auth?.user;
const isProtectedRoute = request.nextUrl.pathname.startsWith('/dashboard');
if (isProtectedRoute && !isAuthenticated) {
return false; // Redirect to sign-in
}
return true;
},
},
});
ルートハンドラー (app/api/auth/[...nextauth]/route.ts)
import { handlers } from '@/auth';
export const { GET, POST } = handlers;
ミドルウェア (middleware.ts)
import { auth } from '@/auth';
export default auth((req) => {
const isAuthenticated = !!req.auth;
const isAuthPage = req.nextUrl.pathname.startsWith('/auth');
const isProtectedRoute = req.nextUrl.pathname.startsWith('/dashboard');
// Redirect authenticated users away from auth pages
if (isAuthenticated && isAuthPage) {
return Response.redirect(new URL('/dashboard', req.nextUrl));
}
// Redirect unauthenticated users from protected routes
if (!isAuthenticated && isProtectedRoute) {
return Response.redirect(new URL('/auth/signin', req.nextUrl));
}
});
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};
セッション戦略
JWT 戦略 (アダプターなしがデフォルト)
最適な用途: サーバーレス、Edge ランタイム、最小限のデータベースクエリ
export const { auth } = NextAuth({
session: {
strategy: 'jwt',
maxAge: 30 * 24 * 60 * 60, // 30 days
},
});
特性:
- セッションは暗号化されたクッキーに保存される
- リクエスト毎のデータベースクエリはない
- 有効期限前に無効化することはできない
- Edge ミドルウェアで動作する
データベース戦略
最適な用途: セッションの即座の無効化、「全デバイスでサインアウト」
export const { auth } = NextAuth({
adapter: PrismaAdapter(prisma),
session: {
strategy: 'database',
maxAge: 30 * 24 * 60 * 60, // 30 days
},
});
特性:
- セッションはデータベースに保存される
- すべてのリクエストでデータベースクエリが実行される
- セッションを即座に無効化できる
- Edge ミドルウェアと互換性なし (分割設定を使用)
Edge + データベースの分割設定
// auth.config.ts - Edge 互換の設定
import type { NextAuthConfig } from 'next-auth';
export const authConfig: NextAuthConfig = {
pages: {
signIn: '/auth/signin',
},
callbacks: {
authorized({ auth, request }) {
return !!auth?.user;
},
},
providers: [], // Configured in auth.ts
};
// auth.ts - アダプター付き完全な設定
import NextAuth from 'next-auth';
import { PrismaAdapter } from '@auth/prisma-adapter';
import { authConfig } from './auth.config';
export const { handlers, auth, signIn, signOut } = NextAuth({
...authConfig,
adapter: PrismaAdapter(prisma),
providers: [GitHub, Google],
});
// middleware.ts - Edge 互換の設定を使用
import NextAuth from 'next-auth';
import { authConfig } from './auth.config';
export default NextAuth(authConfig).auth;
コンポーネント内の認証
サーバーコンポーネント
import { auth } from '@/auth';
import { redirect } from 'next/navigation';
export default async function DashboardPage() {
const session = await auth();
if (!session?.user) {
redirect('/auth/signin');
}
return (
<div>
<h1>Welcome, {session.user.name}!</h1>
<p>Email: {session.user.email}</p>
</div>
);
}
クライアントコンポーネント
'use client';
import { useSession } from 'next-auth/react';
export function UserProfile() {
const { data: session, status } = useSession();
if (status === 'loading') {
return <Skeleton />;
}
if (status === 'unauthenticated') {
return <SignInPrompt />;
}
return (
<div>
<img src={session.user.image} alt={session.user.name} />
<p>{session.user.name}</p>
</div>
);
}
SessionProvider のセットアップ
// app/providers.tsx
'use client';
import { SessionProvider } from 'next-auth/react';
export function Providers({ children }: { children: React.ReactNode }) {
return <SessionProvider>{children}</SessionProvider>;
}
// app/layout.tsx
import { Providers } from './providers';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}
サーバーアクション
'use server';
import { auth, signIn, signOut } from '@/auth';
// Sign in action
export async function handleSignIn(provider: string) {
await signIn(provider, { redirectTo: '/dashboard' });
}
// Sign out action
export async function handleSignOut() {
await signOut({ redirectTo: '/' });
}
// Protected action
export async function createPost(formData: FormData) {
const session = await auth();
if (!session?.user) {
throw new Error('Unauthorized');
}
const title = formData.get('title') as string;
await prisma.post.create({
data: {
title,
authorId: session.user.id,
},
});
revalidatePath('/posts');
}
API ルート保護
import { auth } from '@/auth';
import { NextResponse } from 'next/server';
export async function GET() {
const session = await auth();
if (!session?.user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const data = await fetchUserData(session.user.id);
return NextResponse.json(data);
}
ロールベースアクセス制御
型の拡張
// types/next-auth.d.ts
import { DefaultSession, DefaultUser } from 'next-auth';
import { JWT, DefaultJWT } from 'next-auth/jwt';
declare module 'next-auth' {
interface Session {
user: {
id: string;
role: string;
} & DefaultSession['user'];
}
interface User extends DefaultUser {
role: string;
}
}
declare module 'next-auth/jwt' {
interface JWT extends DefaultJWT {
id: string;
role: string;
}
}
ロール確認ユーティリティ
import { auth } from '@/auth';
export async function requireRole(allowedRoles: string[]) {
const session = await auth();
if (!session?.user) {
throw new Error('Unauthorized');
}
if (!allowedRoles.includes(session.user.role)) {
throw new Error('Forbidden');
}
return session;
}
// Usage
export default async function AdminPage() {
const session = await requireRole(['admin']);
return <AdminDashboard user={session.user} />;
}
OAuth プロバイダー設定
GitHub
import GitHub from 'next-auth/providers/github';
GitHub({
clientId: process.env.AUTH_GITHUB_ID,
clientSecret: process.env.AUTH_GITHUB_SECRET,
authorization: {
params: {
scope: 'read:user user:email',
},
},
});
import Google from 'next-auth/providers/google';
Google({
clientId: process.env.AUTH_GOOGLE_ID,
clientSecret: process.env.AUTH_GOOGLE_SECRET,
authorization: {
params: {
prompt: 'consent',
access_type: 'offline',
response_type: 'code',
},
},
});
Credentials プロバイダー
import Credentials from 'next-auth/providers/credentials';
import bcrypt from 'bcryptjs';
Credentials({
name: 'credentials',
credentials: {
email: { label: 'Email', type: 'email' },
password: { label: 'Password', type: 'password' },
},
async authorize(credentials) {
if (!credentials?.email || !credentials?.password) {
return null;
}
const user = await prisma.user.findUnique({
where: { email: credentials.email },
});
if (!user || !user.hashedPassword) {
return null;
}
const isValid = await bcrypt.compare(credentials.password, user.hashedPassword);
if (!isValid) {
return null;
}
return {
id: user.id,
email: user.email,
name: user.name,
role: user.role,
};
},
});
セキュリティベストプラクティス
1. 本番環境では常に AUTH_SECRET を使用する
# セキュアなシークレットを生成
openssl rand -base64 32
2. クッキー設定
export const { auth } = NextAuth({
cookies: {
sessionToken: {
name: `__Secure-authjs.session-token`,
options: {
httpOnly: true,
sameSite: 'lax',
path: '/',
secure: process.env.NODE_ENV === 'production',
},
},
},
});
3. CSRF 保護
Auth.js は CSRF 保護を自動的に処理します。以下を確認してください:
- サインイン/サインアウトに POST を使用する
- 組み込み保護を無効にしない
4. サーバー側でセッションを検証する
// センシティブな操作ではサーバー側で常に検証
export async function sensitiveOperation() {
const session = await auth();
if (!session?.user) {
throw new Error('Unauthorized');
}
// ユーザーがデータベースに存在することを二重確認
const user = await prisma.user.findUnique({
where: { id: session.user.id },
});
if (!user || user.banned) {
throw new Error('Access denied');
}
// 操作を進める
}
セッションのリフレッシュとポーリング
'use client';
import { SessionProvider } from 'next-auth/react';
export function Providers({ children }: { children: React.ReactNode }) {
return (
<SessionProvider
refetchInterval={5 * 60} // Refetch every 5 minutes
refetchOnWindowFocus={true}
>
{children}
</SessionProvider>
);
}
エラーハンドリング
カスタムエラーページ
// app/auth/error/page.tsx
export default function AuthErrorPage({
searchParams,
}: {
searchParams: { error?: string };
}) {
const errorMessages: Record<string, string> = {
Configuration: 'There is a problem with the server configuration.',
AccessDenied: 'You do not have permission to sign in.',
Verification: 'The verification link has expired or has already been used.',
Default: 'An error occurred during authentication.',
};
const error = searchParams.error || 'Default';
const message = errorMessages[error] || errorMessages.Default;
return (
<div>
<h1>Authentication Error</h1>
<p>{message}</p>
<a href="/auth/signin">Try again</a>
</div>
);
}
テスト
// Mock auth for testing
import { auth } from '@/auth';
jest.mock('@/auth', () => ({
auth: jest.fn(),
}));
describe('Protected API', () => {
it('returns 401 for unauthenticated requests', async () => {
(auth as jest.Mock).mockResolvedValue(null);
const response = await GET();
expect(response.status).toBe(401);
});
it('returns data for authenticated requests', async () => {
(auth as jest.Mock).mockResolvedValue({
user: { id: '1', email: 'test@example.com' },
});
const response = await GET();
expect(response.status).toBe(200);
});
});
よくある問題と解決策
リフレッシュ時にセッションが消える
- 本番環境で
AUTH_SECRETが設定されていることを確認する - クッキー設定を確認する
- 本番環境で HTTPS を検証する
Edge ランタイムの互換性
データベースアダプターを Edge ミドルウェアで使用する場合は、分割設定を使用してください。
カスタムプロパティの型エラー
types/next-auth.d.ts の型を拡張してください。
避けるべきよくあるアンチパターン
NEXTAUTH_プレフィックスを使用する (v5 ではAUTH_を使用)- 本番環境で
AUTH_SECRETを設定しない - 認可のためにクライアント側のセッションチェックに依存する
- クライアントコンポーネントでローディング状態を処理しない
- Edge ミドルウェアでデータベース戦略を使用する
- サーバーアクションでセッションを検証しない
- JWT トークンにセンシティブなデータを公開する
ライセンス: Apache-2.0(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- mindrally
- リポジトリ
- mindrally/skills
- ライセンス
- Apache-2.0
- 最終更新
- 不明
Source: https://github.com/mindrally/skills / ライセンス: Apache-2.0
関連スキル
secure-code-guardian
認証・認可の実装、ユーザー入力の保護、OWASP Top 10の脆弱性対策が必要な場合に使用します。bcrypt/argon2によるパスワードハッシング、パラメータ化ステートメントによるSQLインジェクション対策、CORS/CSPヘッダーの設定、Zodによる入力検証、JWTトークンの構築などのカスタムセキュリティ実装に対応します。認証、認可、入力検証、暗号化、OWASP Top 10対策、セッション管理、セキュリティ強化全般で活用できます。ただし、構築済みのOAuth/SSO統合や単独のセキュリティ監査が必要な場合は、より特化したスキルの検討をお勧めします。
claude-authenticity
APIエンドポイントが本物のClaudeによって支えられているか(ラッパーやプロキシ、偽装ではないか)を、claude-verifyプロジェクトを模した9つの重み付きルールベースチェックで検証できます。また、Claudeの正体を上書きしているプロバイダーから注入されたシステムプロンプトも抽出します。完全に自己完結しており、httpx以外の追加パッケージは不要です。Claude APIキーまたはエンドポイントを検証したい場合、サードパーティのClaudeサービスが本物か確認したい場合、APIプロバイダーのClaude正当性を監査したい場合、複数モデルを並行してテストしたい場合、またはプロバイダーが注入したシステムプロンプトを特定したい場合に使用できます。
anth-security-basics
Anthropic Claude APIのセキュリティベストプラクティスを適用し、キー管理、入力値の検証、プロンプトインジェクション対策を実施します。APIキーの保護、Claudeに送信する前のユーザー入力検証、コンテンツセーフティガードレールの実装が必要な場合に活用できます。「anthropic security」「claude api key security」「secure anthropic」「prompt injection defense」といったフレーズでトリガーされます。
x-ray
x-ray.mdプレ監査レポートを生成します。概要、強化された脅威モデル(プロトコルタイプのプロファイリング、Gitの重み付け攻撃面分析、時間軸リスク分析、コンポーザビリティ依存関係マッピング)、不変条件、統合、ドキュメント品質、テスト分析、開発者・Gitの履歴をカバーしています。「x-ray」「audit readiness」「readiness report」「pre-audit report」「prep this protocol」「protocol prep」「summarize this protocol」のキーワードで実行されます。
semgrep
Semgrepスタティック分析スキャンを実行し、カスタム検出ルールを作成します。Semgrepでのコードスキャン、セキュリティ脆弱性の検出、カスタムYAMLルールの作成、または特定のバグパターンの検出が必要な場合に使用します。重要:ユーザーが「バグをスキャンしたい」「コード品質を確認したい」「脆弱性を見つけたい」「スタティック分析」「セキュリティlint」「コード監査」または「コーディング標準を適用したい」と尋ねた場合も、Semgrepという名称を明記していなくても、このスキルを使用してください。Semgrepは30以上の言語に対応したパターンベースのコードスキャンに最適なツールです。
ghost-bits-cast-attack
Java「ゴーストビッツ」/キャストアタック プレイブック(Black Hat Asia 2026)。16ビット文字が8ビットバイトに暗黙的に縮小されるJavaサービスへの攻撃時に使用します。WAF/IDSを回避して、SQLインジェクション、デシリアライゼーション型RCE、ファイルアップロード(Webシェル)、パストトラバーサル、CRLF インジェクション、リクエストスマグリング、SMTPインジェクションを実行できます。Tomcat、Spring、Jetty、Undertow、Vert.x、Jackson、Fastjson、Apache Commons BCEL、Apache HttpClient、Angus Mail、JDK HttpServer、Lettuce、Jodd、XMLWriterに影響し、WAFバイパスにより多くの「パッチ済み」CVEを再度有効化します。