auth-patterns について
auth-patterns は、Next.js アプリケーション開発者向けの認証スキルです。NextAuth.js (Auth.js)、ミドルウェアによる保護、セッション、ロールベースのアクセス制御に加え、保存方式やセキュリティの実践方法を扱います。
主な機能
- 01NextAuth.js v5 の設定
- 02ミドルウェアによるルート保護
- 03サーバーとクライアントでのセッション取得
- 04ロールベースのアクセス制御
- 05JWT とデータベースセッション
- 06認証のセキュリティ対策
活用シーン
- 01Next.js アプリへのログイン追加
- 02ダッシュボードや API ルートの保護
- 03ユーザーロール別のページ制限
- 04セッション保存方式の選択
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ davepoon/ buildwithclaude --skill auth-patternsインストール後、Claude Code で/auth-patternsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
examples/nextauth-setup.mdreferences/middleware-auth.mdsession-management.md詳細
Next.js の認証パターン
概要
Next.js では、複数の認証方式を利用できます。このスキルでは、NextAuth.js(Auth.js)、ミドルウェアベースの保護、セッション管理など、一般的なパターンを取り上げます。
認証ライブラリ
| ライブラリ | 最適な用途 |
|---|---|
| NextAuth.js (Auth.js) | プロバイダーを備えた多機能な認証 |
| Clerk | マネージド認証サービス |
| Lucia | 軽量で柔軟な認証 |
| Supabase Auth | Supabase エコシステム |
| Custom JWT | 完全な制御 |
NextAuth.js v5 のセットアップ
インストール
npm install next-auth@beta
設定
// auth.ts
import NextAuth from 'next-auth'
import GitHub from 'next-auth/providers/github'
import Credentials from 'next-auth/providers/credentials'
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
GitHub({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
}),
Credentials({
credentials: {
email: { label: 'Email', type: 'email' },
password: { label: 'Password', type: 'password' },
},
authorize: async (credentials) => {
const user = await getUserByEmail(credentials.email)
if (!user || !verifyPassword(credentials.password, user.password)) {
return null
}
return user
},
}),
],
callbacks: {
authorized: async ({ auth }) => {
return !!auth
},
},
})
API ルートハンドラー
// app/api/auth/[...nextauth]/route.ts
import { handlers } from '@/auth'
export const { GET, POST } = handlers
ミドルウェアによる保護
// middleware.ts
export { auth as middleware } from '@/auth'
export const config = {
matcher: ['/dashboard/:path*', '/api/protected/:path*'],
}
セッションデータの取得
サーバーコンポーネントの場合
// app/dashboard/page.tsx
import { auth } from '@/auth'
import { redirect } from 'next/navigation'
export default async function DashboardPage() {
const session = await auth()
if (!session) {
redirect('/login')
}
return (
<div>
<h1>Welcome, {session.user?.name}</h1>
</div>
)
}
クライアントコンポーネントの場合
// components/user-menu.tsx
'use client'
import { useSession } from 'next-auth/react'
export function UserMenu() {
const { data: session, status } = useSession()
if (status === 'loading') {
return <div>Loading...</div>
}
if (!session) {
return <SignInButton />
}
return (
<div>
<span>{session.user?.name}</span>
<SignOutButton />
</div>
)
}
セッションプロバイダーのセットアップ
// 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 }) {
return (
<html>
<body>
<Providers>{children}</Providers>
</body>
</html>
)
}
サインイン/サインアウトコンポーネント
// components/auth-buttons.tsx
import { signIn, signOut } from '@/auth'
export function SignInButton() {
return (
<form
action={async () => {
'use server'
await signIn('github')
}}
>
<button type="submit">Sign in with GitHub</button>
</form>
)
}
export function SignOutButton() {
return (
<form
action={async () => {
'use server'
await signOut()
}}
>
<button type="submit">Sign out</button>
</form>
)
}
ミドルウェアベースの認証
基本パターン
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
const protectedRoutes = ['/dashboard', '/settings', '/api/protected']
const authRoutes = ['/login', '/signup']
export function middleware(request: NextRequest) {
const token = request.cookies.get('session')?.value
const { pathname } = request.nextUrl
// Redirect authenticated users away from auth pages
if (authRoutes.some(route => pathname.startsWith(route))) {
if (token) {
return NextResponse.redirect(new URL('/dashboard', request.url))
}
return NextResponse.next()
}
// Protect routes
if (protectedRoutes.some(route => pathname.startsWith(route))) {
if (!token) {
const loginUrl = new URL('/login', request.url)
loginUrl.searchParams.set('callbackUrl', pathname)
return NextResponse.redirect(loginUrl)
}
}
return NextResponse.next()
}
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
}
JWT 検証を使用する場合
// middleware.ts
import { NextResponse } from 'next/server'
import { jwtVerify } from 'jose'
const secret = new TextEncoder().encode(process.env.JWT_SECRET)
export async function middleware(request: NextRequest) {
const token = request.cookies.get('token')?.value
if (!token) {
return NextResponse.redirect(new URL('/login', request.url))
}
try {
const { payload } = await jwtVerify(token, secret)
// Token is valid, continue
return NextResponse.next()
} catch {
// Token is invalid
return NextResponse.redirect(new URL('/login', request.url))
}
}
ロールベースのアクセス制御
セッション型の拡張
// types/next-auth.d.ts
import { DefaultSession } from 'next-auth'
declare module 'next-auth' {
interface Session {
user: {
role: 'user' | 'admin'
} & DefaultSession['user']
}
}
// auth.ts
export const { handlers, auth } = NextAuth({
callbacks: {
session: ({ session, token }) => ({
...session,
user: {
...session.user,
role: token.role,
},
}),
jwt: ({ token, user }) => {
if (user) {
token.role = user.role
}
return token
},
},
})
ロールベースのコンポーネント
// components/admin-only.tsx
import { auth } from '@/auth'
import { redirect } from 'next/navigation'
export async function AdminOnly({ children }: { children: React.ReactNode }) {
const session = await auth()
if (session?.user?.role !== 'admin') {
redirect('/unauthorized')
}
return <>{children}</>
}
// Usage
export default async function AdminPage() {
return (
<AdminOnly>
<AdminDashboard />
</AdminOnly>
)
}
セッションの保存オプション
JWT(ステートレス)
// auth.ts
export const { auth } = NextAuth({
session: { strategy: 'jwt' },
// JWT stored in cookies, no database needed
})
データベースセッション
// auth.ts
import { PrismaAdapter } from '@auth/prisma-adapter'
import { prisma } from '@/lib/prisma'
export const { auth } = NextAuth({
adapter: PrismaAdapter(prisma),
session: { strategy: 'database' },
// Sessions stored in database
})
カスタムログインページ
// app/login/page.tsx
'use client'
import { signIn } from 'next-auth/react'
import { useSearchParams } from 'next/navigation'
export default function LoginPage() {
const searchParams = useSearchParams()
const callbackUrl = searchParams.get('callbackUrl') || '/dashboard'
return (
<div className="flex flex-col gap-4">
<button
onClick={() => signIn('github', { callbackUrl })}
className="btn"
>
Sign in with GitHub
</button>
<button
onClick={() => signIn('google', { callbackUrl })}
className="btn"
>
Sign in with Google
</button>
</div>
)
}
セキュリティのベストプラクティス
- 本番環境では HTTPS を使用します
- セキュアな Cookie フラグ(HttpOnly、Secure、SameSite)を設定します
- CSRF 保護を実装します(NextAuth に組み込み済み)
- オープンリダイレクトを防ぐために、リダイレクト URL を検証します
- シークレットには環境変数を使用します
- 認証エンドポイントにレート制限を実装します
- bcrypt または argon2 でパスワードをハッシュ化します
リソース
詳しいパターンについては、以下を参照してください。
references/middleware-auth.md- 高度なミドルウェアパターンreferences/session-management.md- セッション方式examples/nextauth-setup.md- NextAuth.js の完全なセットアップ
コメント
Markdown 対応