Supabase Authで認証・認可を実装する
このページで学ぶこと
- 認証と認可の違いがわかる
- Supabase Auth でメール認証を実装できる
- ログイン済みユーザーのみアクセスできるページを作れる
認証と認可の違い
| 用語 | 意味 | 例 |
|---|---|---|
| 認証(Authentication) | 「あなたは誰?」を確認する | ログイン |
| 認可(Authorization) | 「あなたに権限はある?」を確認する | 自分のTodoしか編集できない |
Supabase Auth のセットアップ
ステップ1: パッケージをインストール
npm install @supabase/ssr @supabase/supabase-js
@supabase/supabase-jsはすでにインストール済みのはずです。
ステップ2: Supabase クライアントを2種類用意する
Supabase SSR では「ブラウザ用」と「サーバー用」の2種類のクライアントを使います。
ブラウザ用(フロントエンドのコンポーネントで使う)
// lib/supabase/client.js
import { createBrowserClient } from '@supabase/ssr'
export function createClient() {
return createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
)
}サーバー用(APIルートで使う)
// lib/supabase/server.js
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'
export async function createClient() {
const cookieStore = await cookies() // Next.js 16 では await が必要
return createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
{
cookies: {
getAll: () => cookieStore.getAll(),
setAll: (cookiesToSet) => {
cookiesToSet.forEach(({ name, value, options }) =>
cookieStore.set(name, value, options)
)
},
},
}
)
}ステップ3: middleware.js を作成する
Supabase Auth ではセッション(ログイン状態)をCookieで管理します。
middleware.js を作成しないと、サーバー側でログイン状態が正しく認識されません。
// middleware.js(プロジェクトのルートに置く)
import { createServerClient } from '@supabase/ssr'
import { NextResponse } from 'next/server'
export async function middleware(request) {
let supabaseResponse = NextResponse.next({ request })
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
{
cookies: {
getAll: () => request.cookies.getAll(),
setAll: (cookiesToSet) => {
cookiesToSet.forEach(({ name, value }) =>
request.cookies.set(name, value)
)
supabaseResponse = NextResponse.next({ request })
cookiesToSet.forEach(({ name, value, options }) =>
supabaseResponse.cookies.set(name, value, options)
)
},
},
}
)
// セッションを更新する(これをしないとログイン状態が切れる)
await supabase.auth.getUser()
return supabaseResponse
}
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
}ステップ4: Supabase ダッシュボードでメール確認を無効化する(開発時)
デフォルトではサインアップ時に確認メールが送られます。
開発中は毎回メールを確認するのが面倒なので、一時的に無効化しましょう。
- Supabase ダッシュボード → Authentication → Email Templates
- 「Confirm signup」の設定を確認
- または Authentication → Providers → Email → 「Confirm email」をオフにする
本番環境では必ず有効に戻してください。
やってみよう
ステップ1: サインアップページを作る
// app/signup/page.js
'use client'
import { useState } from 'react'
import { createClient } from '../../lib/supabase/client'
export default function SignupPage() {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [message, setMessage] = useState('')
const supabase = createClient()
const handleSignup = async () => {
const { error } = await supabase.auth.signUp({ email, password })
if (error) {
setMessage(`エラー: ${error.message}`)
} else {
setMessage('登録しました。ログインページからログインしてください。')
}
}
return (
<div style={{ padding: '32px', maxWidth: '400px' }}>
<h1>新規登録</h1>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="メールアドレス"
style={{ display: 'block', width: '100%', padding: '8px', marginBottom: '8px' }}
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="パスワード(6文字以上)"
style={{ display: 'block', width: '100%', padding: '8px', marginBottom: '16px' }}
/>
<button onClick={handleSignup} style={{ width: '100%', padding: '8px' }}>
登録する
</button>
{message && <p style={{ marginTop: '16px' }}>{message}</p>}
</div>
)
}ステップ2: ログインページを作る
// app/login/page.js
'use client'
import { useState } from 'react'
import { useRouter } from 'next/navigation'
import { createClient } from '../../lib/supabase/client'
export default function LoginPage() {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [error, setError] = useState('')
const router = useRouter()
const supabase = createClient()
const handleLogin = async () => {
const { error } = await supabase.auth.signInWithPassword({ email, password })
if (error) {
setError('メールアドレスまたはパスワードが間違っています')
} else {
router.push('/')
}
}
return (
<div style={{ padding: '32px', maxWidth: '400px' }}>
<h1>ログイン</h1>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="メールアドレス"
style={{ display: 'block', width: '100%', padding: '8px', marginBottom: '8px' }}
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="パスワード"
style={{ display: 'block', width: '100%', padding: '8px', marginBottom: '16px' }}
/>
{error && <p style={{ color: 'red' }}>{error}</p>}
<button onClick={handleLogin} style={{ width: '100%', padding: '8px' }}>
ログイン
</button>
</div>
)
}ステップ3: ログイン状態を確認してページを保護する
// app/page.js(保護されたトップページ)
'use client'
import { useState, useEffect } from 'react'
import { useRouter } from 'next/navigation'
import { createClient } from '../lib/supabase/client'
export default function HomePage() {
const [user, setUser] = useState(null)
const [loading, setLoading] = useState(true)
const router = useRouter()
const supabase = createClient()
useEffect(() => {
supabase.auth.getUser().then(({ data }) => {
if (!data.user) {
router.push('/login')
} else {
setUser(data.user)
}
setLoading(false)
})
}, [])
const handleLogout = async () => {
await supabase.auth.signOut()
router.push('/login')
}
if (loading) return <p>読み込み中...</p>
return (
<div style={{ padding: '32px' }}>
<h1>ホーム</h1>
<p>ようこそ、{user?.email} さん</p>
<button onClick={handleLogout}>ログアウト</button>
</div>
)
}🔒 認可:「自分のデータしか操作できない」
ログインユーザーのIDを使って、自分のTodoだけ取得・操作できるようにします。
まず prisma/schema.prisma の Todo モデルの userId を String に変更します(Supabase Auth のユーザーIDは UUID = 文字列型です)。
model Todo {
id Int @id @default(autoincrement())
title String
completed Boolean @default(false)
userId String @map("user_id") // ← Int から String に変更
createdAt DateTime @default(now()) @map("created_at")
@@map("todos")
}変更したらマイグレーションを実行します。
npx prisma migrate dev --name add-string-user-id次に、APIルートでログインユーザーのIDを取得して絞り込みます。
// app/api/todos/route.js(認証チェックを追加)
import { NextResponse } from 'next/server'
import { prisma } from '../../../lib/prisma'
import { createClient } from '../../../lib/supabase/server'
export async function GET() {
const supabase = await createClient()
const { data: { user } } = await supabase.auth.getUser()
if (!user) {
return NextResponse.json({ error: 'ログインが必要です' }, { status: 401 })
}
// ✅ ログインユーザーのTodoのみ取得
const todos = await prisma.todo.findMany({
where: { userId: user.id },
orderBy: { createdAt: 'desc' },
})
return NextResponse.json(todos)
}
export async function POST(request) {
const supabase = await createClient()
const { data: { user } } = await supabase.auth.getUser()
if (!user) {
return NextResponse.json({ error: 'ログインが必要です' }, { status: 401 })
}
const { title } = await request.json()
if (!title?.trim()) {
return NextResponse.json({ error: 'titleは必須です' }, { status: 400 })
}
const todo = await prisma.todo.create({
data: {
title: title.trim(),
userId: user.id, // ✅ ログインユーザーのIDを使う
},
})
return NextResponse.json(todo, { status: 201 })
}確認しよう
-
middleware.jsをプロジェクトルートに作成した - サインアップしてログインできた
- ログアウト後にログインページにリダイレクトされた
- ログイン後、自分のTodoだけが表示される
- 認証と認可の違いを説明できる
AIに聞いてみよう
「JWTとは何ですか?Supabase Authはどのようにセッションを管理していますか?」
次のステップ
バックエンドセクションはここで完了です!いよいよ自分のアプリを作りましょう。
Last updated on