nextjs-supabase-auth について
Next.js App Router で Supabase Auth を統合するパターン集です。ログイン、セッション管理、保護されたルートを実装する開発者向けに、クライアント設定、OAuth コールバック、安全性の確認方法を示します。
主な機能
- 01ブラウザー用・サーバー用クライアントの設定
- 02ミドルウェアによるセッション更新とルート保護
- 03OAuth コールバックの処理
- 04Server Actions による認証処理
- 05一般的な認証エラーの確認
活用シーン
- 01Next.js アプリへの Supabase ログイン追加
- 02セッション更新とダッシュボードへのアクセス制限
- 03OAuth ログインコールバックの処理
- 04サーバー認証とリダイレクトの確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ sickn33/ agentic-awesome-skills --skill nextjs-supabase-authインストール後、Claude Code で/nextjs-supabase-authを入力して読み込まれたか確認します。
詳細
Next.js + Supabase Auth
Supabase Auth と Next.js App Router の高度な統合
機能
- nextjs-auth
- supabase-auth-nextjs
- auth-middleware
- auth-callback
前提条件
- 必要なスキル:nextjs-app-router、supabase-backend
パターン
Supabase クライアントのセットアップ
コンテキストごとに適切に設定された Supabase クライアントを作成します
使用する場面:Next.js プロジェクトで認証をセットアップするとき
// lib/supabase/client.ts (Browser client) 'use client' import { createBrowserClient } from '@supabase/ssr'
export function createClient() { return createBrowserClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! ) }
// lib/supabase/server.ts (Server client) import { createServerClient } from '@supabase/ssr' import { cookies } from 'next/headers'
export async function createClient() { const cookieStore = await cookies() return createServerClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, { cookies: { getAll() { return cookieStore.getAll() }, setAll(cookiesToSet) { // This helper is for a Route Handler/Server Action with writable cookies. // In a Server Component use the official read-only client pattern and // refresh sessions in middleware/proxy; do not attempt cookie writes here. cookiesToSet.forEach(({ name, value, options }) => cookieStore.set(name, value, options)) }, }, } ) }
認証ミドルウェア
ミドルウェアでルートを保護し、セッションを更新します
使用する場面:ルート保護またはセッション更新が必要なとき
// middleware.ts import { createServerClient } from '@supabase/ssr' import { NextResponse, type NextRequest } from 'next/server'
export async function middleware(request: NextRequest) { let response = NextResponse.next({ request })
const supabase = createServerClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, { cookies: { getAll() { return request.cookies.getAll() }, setAll(cookiesToSet) { cookiesToSet.forEach(({ name, value }) => request.cookies.set(name, value)) response = NextResponse.next({ request }) cookiesToSet.forEach(({ name, value, options }) => response.cookies.set(name, value, options)) }, }, } )
// Refresh session if expired const { data: { user } } = await supabase.auth.getUser()
// Protect dashboard routes if (request.nextUrl.pathname.startsWith('/dashboard') && !user) { return NextResponse.redirect(new URL('/login', request.url)) }
return response }
export const config = { matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'], }
認証コールバックルート
OAuth コールバックを処理し、コードをセッションと交換します
使用する場面:OAuth プロバイダー(Google、GitHub など)を使用するとき
// app/auth/callback/route.ts import { createClient } from '@/lib/supabase/server' import { NextResponse } from 'next/server'
export async function GET(request: Request) { const { searchParams, origin } = new URL(request.url) const code = searchParams.get('code') const candidate = searchParams.get('next') ?? '/' const next = candidate.startsWith('/') && !candidate.startsWith('//') && !candidate.includes('\') ? candidate : '/'
if (code) { const supabase = await createClient() const { error } = await supabase.auth.exchangeCodeForSession(code) if (!error) { return NextResponse.redirect(new URL(next, process.env.APP_ORIGIN!)) // 検証済みの信頼できるデプロイ元 } }
return NextResponse.redirect(${origin}/auth/error)
}
サーバーアクションでの認証
サーバーアクションで認証操作を処理します
使用する場面: サーバーコンポーネントからのログイン、ログアウト、またはサインアップ
// app/actions/auth.ts 'use server' import { createClient } from '@/lib/supabase/server' import { redirect } from 'next/navigation' import { revalidatePath } from 'next/cache'
export async function signIn(formData: FormData) { const supabase = await createClient() const { error } = await supabase.auth.signInWithPassword({ email: formData.get('email') as string, password: formData.get('password') as string, })
if (error) { return { error: error.message } }
revalidatePath('/', 'layout') redirect('/dashboard') }
export async function signOut() { const supabase = await createClient() await supabase.auth.signOut() revalidatePath('/', 'layout') redirect('/') }
サーバーコンポーネントでユーザーを取得する
サーバーコンポーネントで認証済みユーザーにアクセスします
使用する場面: ユーザー固有のコンテンツをサーバーサイドでレンダリングする場合
// app/dashboard/page.tsx import { createClient } from '@/lib/supabase/server' import { redirect } from 'next/navigation'
export default async function DashboardPage() { const supabase = await createClient() const { data: { user } } = await supabase.auth.getUser()
if (!user) { redirect('/login') }
return (
Welcome, {user.email}
検証チェック
認証チェックに getSession() を使用している
重大度: ERROR
メッセージ: getSession() は JWT を検証しません。安全な認証チェックには getUser() を使用してください。
修正アクション: セキュリティ上重要なチェックでは getSession() を getUser() に置き換えてください
コールバックルートなしの OAuth
重大度: ERROR
メッセージ: OAuth を使用していますが、app/auth/callback/route.ts にコールバックルートがありません
修正アクション: OAuth リダイレクトを処理するために app/auth/callback/route.ts を作成してください
サーバーコンテキストでのブラウザクライアント
重大度: ERROR
メッセージ: サーバーコンテキストでブラウザクライアントが使用されています。代わりに createServerClient を使用してください。
修正アクション: @supabase/ssr から createServerClient をインポートして使用してください
ミドルウェアなしの保護されたルート
重大度: WARNING
メッセージ: middleware.ts が見つかりません。ルート保護のためにミドルウェアの追加を検討してください。
修正アクション: ルートを保護しセッションを更新するために middleware.ts を作成してください
ハードコードされた認証リダイレクト URL
重大度: WARNING
メッセージ: ハードコードされた localhost リダイレクトです。環境の柔軟性のために origin を使用してください。
修正アクション: window.location.origin または process.env.NEXT_PUBLIC_SITE_URL を使用してください
エラーハンドリングなしの認証呼び出し
重大度: WARNING
メッセージ: エラーハンドリングなしの認証操作です。常にエラーをチェックしてください。
修正アクション: { data, error } を分割代入し、エラーケースを処理してください
再検証なしの認証アクション
重大度: WARNING
メッセージ: revalidatePath なしの認証アクションです。キャッシュに古い認証状態が表示される可能性があります。
修正アクション: 認証操作の後に revalidatePath('/', 'layout') を追加してください
クライアントのみのルート保護
重大度: WARNING
メッセージ: クライアントサイドのルート保護ではコンテンツが一瞬表示されます。ミドルウェアを使用してください。
修正アクション: より良い UX のために保護を middleware.ts に移動してください
連携
委譲トリガー
- database|rls|queries|tables -> supabase-backend (認証にはデータベースレイヤーが必要)
- route|page|component|layout -> nextjs-app-router (認証には Next.js のパターンが必要)
- deploy|production|vercel -> vercel-deployment (認証にはデプロイ設定が必要)
- ui|form|button|design -> frontend (認証には UI コンポーネントが必要)
フル認証スタック
スキル: nextjs-supabase-auth, supabase-backend, nextjs-app-router, vercel-deployment
ワークフロー:
1. Database setup (supabase-backend)
2. Auth implementation (nextjs-supabase-auth)
3. Route protection (nextjs-app-router)
4. Deployment config (vercel-deployment)
保護された SaaS
スキル: nextjs-supabase-auth, stripe-integration, supabase-backend
ワークフロー:
1. User authentication (nextjs-supabase-auth)
2. Customer sync (stripe-integration)
3. Subscription gating (supabase-backend)
関連スキル
相性の良い組み合わせ: nextjs-app-router、supabase-backend
使用する場面
- ユーザーが「supabase auth next」に言及、またはそれを示唆している
- ユーザーが「authentication next.js」に言及、またはそれを示唆している
- ユーザーが「login supabase」に言及、またはそれを示唆している
- ユーザーが「auth middleware」に言及、またはそれを示唆している
- ユーザーが「protected route」に言及、またはそれを示唆している
- ユーザーが「auth callback」に言及、またはそれを示唆している
- ユーザーが「session management」に言及、またはそれを示唆している
制限事項
- タスクが上記の範囲に明確に該当する場合にのみ、このスキルを使用してください。
- 出力を、環境固有の検証、テスト、専門家によるレビューの代わりとして扱わないでください。
- 必要な入力、権限、安全上の境界、または成功基準が不足している場合は、作業を中断して確認してください。
これらは個別ファイルの断片であり、認証のデプロイに必要なすべてが含まれているわけではありません。まず、信頼できる APP_ORIGIN、コールバックの許可リスト、正確な Next.js/Supabase のバージョンを設定して検証してください。Server Components では Cookie を書き込めません。任意の更新失敗を抑制するのではなく、現在の公式なコンテキスト別ヘルパーを使用してください。返されるリダイレクトでは、更新された Cookie を保持してください。Middleware は唯一の認可レイヤーではありません。すべてのサーバー側のデータ/アクション経路でセッション/クレームとテナント/リソースの権限を検証し、RLS を適用してください。サービスロールキーをブラウザーに公開しないでください。Supabase のサーバーサイドクライアントに関するガイダンスを参照してください。