Skip to Content
ドキュメントWebコースバックエンド・DBSupabase Authで認証・認可を実装する

Supabase Authで認証・認可を実装する

このページで学ぶこと

  • 認証と認可の違いがわかる
  • Supabase Auth でメール認証を実装できる
  • ログイン済みユーザーのみアクセスできるページを作れる

認証と認可の違い

用語意味
認証(Authentication)「あなたは誰?」を確認するログイン
認可(Authorization)「あなたに権限はある?」を確認する自分のTodoしか編集できない

Supabase Auth のセットアップ

ステップ1: パッケージをインストール

npm install @supabase/ssr @supabase/supabase-js
bash

@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
  )
}
javascript

サーバー用(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)
          )
        },
      },
    }
  )
}
javascript

ステップ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).*)'],
}
javascript

ステップ4: Supabase ダッシュボードでメール確認を無効化する(開発時)

デフォルトではサインアップ時に確認メールが送られます。
開発中は毎回メールを確認するのが面倒なので、一時的に無効化しましょう。

  1. Supabase ダッシュボード → Authentication → Email Templates
  2. 「Confirm signup」の設定を確認
  3. または 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>
  )
}
javascript

ステップ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>
  )
}
javascript

ステップ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>
  )
}
javascript

🔒 認可:「自分のデータしか操作できない」

ログインユーザーのIDを使って、自分のTodoだけ取得・操作できるようにします。

まず prisma/schema.prismaTodo モデルの userIdString に変更します(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")
}
prisma

変更したらマイグレーションを実行します。

npx prisma migrate dev --name add-string-user-id
bash

次に、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 })
}
javascript

確認しよう

  • middleware.js をプロジェクトルートに作成した
  • サインアップしてログインできた
  • ログアウト後にログインページにリダイレクトされた
  • ログイン後、自分のTodoだけが表示される
  • 認証と認可の違いを説明できる

AIに聞いてみよう

「JWTとは何ですか?Supabase Authはどのようにセッションを管理していますか?」


次のステップ

バックエンドセクションはここで完了です!いよいよ自分のアプリを作りましょう。

自分のアプリを企画しよう

Last updated on