Auth(認証)
メール / OAuth / Magic Link / Phone OTP / Anonymous など主要な認証方式に対応。
セッションは JWT で、auth.uid() が RLS と直結。
Auth の中身
- GoTrue(OSS の認証サーバ)が動いている
- ユーザは
auth.usersテーブルに保存 - セッションはJWT(access_token + refresh_token)
- RLS と統合: SQL の中で
auth.uid()でログインユーザの ID が取れる
有効化(Dashboard)
Authentication → Providers で各プロバイダを ON / OFF。
- Email(メール + パスワード / Magic Link)
- Phone(SMS OTP)
- Google / GitHub / Apple / Facebook / Twitter / Discord / Slack / 他多数
- SAML(Enterprise)
- Anonymous(ID は uuid だがログイン不要)
メール + パスワード
サインアップ
const { data, error } = await supabase.auth.signUp({
email: "alice@example.com",
password: "********",
options: {
emailRedirectTo: `${origin}/auth/callback`,
data: { display_name: "Alice" }, // user_metadata に保存
},
})
確認メールが送信される(Dashboard で「Email confirmations」が ON のとき)。
サインイン
const { data, error } = await supabase.auth.signInWithPassword({
email, password,
})
if (error) console.error(error.message)
console.log(data.session, data.user)
サインアウト
await supabase.auth.signOut()
Magic Link
await supabase.auth.signInWithOtp({
email,
options: { emailRedirectTo: `${origin}/auth/callback` },
})
ユーザがメールのリンクをクリックすると /auth/callback に ?code=... 付きで飛ぶ。
サーバ側で exchangeCodeForSession を呼んでセッション確立。
OAuth(Google など)
Provider 設定
- Google Cloud Console / GitHub OAuth Apps で OAuth クライアントを作る
- Redirect URL に
https://<project>.supabase.co/auth/v1/callback - Client ID / Secret を Supabase Dashboard に貼る
クライアントから呼ぶ
await supabase.auth.signInWithOAuth({
provider: "google",
options: {
redirectTo: `${origin}/auth/callback`,
scopes: "email profile",
},
})
Phone OTP(SMS)
// 送信
await supabase.auth.signInWithOtp({ phone: "+819012345678" })
// 検証
await supabase.auth.verifyOtp({
phone: "+819012345678",
token: "123456",
type: "sms",
})
Twilio / MessageBird / Vonage などの SMS プロバイダを Dashboard で設定する必要あり。
Anonymous Sign-in
ログイン不要だがユーザ IDを持たせたい場合。匿名 → 後でメール紐付け可能。
const { data } = await supabase.auth.signInAnonymously()
// data.user.id が振られる
// あとで本登録に昇格
await supabase.auth.updateUser({ email: "alice@example.com", password: "..." })
セッションの取得
// 現在のセッション
const { data: { session } } = await supabase.auth.getSession()
// 現在のユーザ
const { data: { user } } = await supabase.auth.getUser()
console.log(user?.id, user?.email)
getSession() はクライアント側のキャッシュを返すので偽装可能。
サーバで認可判断するときは必ず getUser()(サーバ側で署名検証する)。
セッション変化を監視
const { data: { subscription } } = supabase.auth.onAuthStateChange(
(event, session) => {
// event: "SIGNED_IN" | "SIGNED_OUT" | "TOKEN_REFRESHED" | ...
if (event === "SIGNED_IN") refreshUI(session.user)
}
)
// 解除
subscription.unsubscribe()
JWT の中身
Supabase の access_token を jwt.io でデコードすると:
{
"aud": "authenticated",
"exp": 1731555555,
"iat": 1731551955,
"sub": "uuid-of-user",
"email": "alice@example.com",
"role": "authenticated",
"user_metadata": { "display_name": "Alice" },
"app_metadata": { "provider": "email" }
}
- sub: user ID。
auth.uid()の正体 - role:
anon/authenticated/service_role - user_metadata: ユーザが書き換えられる(フロントから可)
- app_metadata: サーバが書き換える(信頼できる)— ロールや権限はここに置く
パスワードリセット
// メール送信
await supabase.auth.resetPasswordForEmail(email, {
redirectTo: `${origin}/auth/reset`,
})
// 新パスワード設定(リダイレクト先で)
await supabase.auth.updateUser({ password: newPassword })
メール変更
await supabase.auth.updateUser({ email: "new@example.com" })
// 旧 / 新両方に確認メール
MFA(多要素認証)
// TOTP の登録
const { data } = await supabase.auth.mfa.enroll({ factorType: "totp" })
// data.totp.qr_code をユーザに表示
// チャレンジ
const { data: challenge } = await supabase.auth.mfa.challenge({ factorId })
// 検証
await supabase.auth.mfa.verify({
factorId,
challengeId: challenge.id,
code: "123456",
})
Next.js App Router での認証フロー
サインインボタン(Client Component)
"use client"
import { supabase } from "@/lib/supabase/client"
export function SignInButton() {
return (
<button onClick={() => supabase.auth.signInWithOAuth({
provider: "google",
options: { redirectTo: `${location.origin}/auth/callback` },
})}>
Sign in with Google
</button>
)
}
OAuth コールバック(Route Handler)
// app/auth/callback/route.ts
import { createClient } from "@/lib/supabase/server"
import { NextResponse } from "next/server"
export async function GET(req: Request) {
const url = new URL(req.url)
const code = url.searchParams.get("code")
if (code) {
const supabase = await createClient()
await supabase.auth.exchangeCodeForSession(code)
}
return NextResponse.redirect(new URL("/", req.url))
}
保護したいページ(Server Component)
// 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 <div>Welcome, {user.email}</div>
}
RLS と auth.uid()
ログイン中のユーザ ID を SQL から参照できる:
create policy "user can read own posts"
on posts for select
to authenticated
using (auth.uid() = author_id);
詳細は RLS ページで。
カスタムクレーム / ロール管理
「管理者だけアクセスできるテーブル」のような場合、app_metadata.role に "admin" を入れて、
SQL から参照する。
-- カスタム関数で取得
create or replace function auth.is_admin()
returns boolean
language sql security definer
as $$
select coalesce(
(auth.jwt() -> 'app_metadata' ->> 'role') = 'admin',
false
)
$$;
app_metadata はservice_role でしか書けないので安全。
セキュリティ Tips
- パスワードの最小強度を Auth Settings で設定
- HaveIBeenPwned 連携で漏洩済みパスワードを拒否
- レート制限: 連続失敗で一時ブロック
- Email Templates をカスタマイズ(フィッシング対策)
- Captcha(hCaptcha / Cloudflare Turnstile)有効化
- JWT Secret は絶対に外に漏らさない
料金 / 制限
- Free: 50,000 MAU(月間アクティブ)
- Pro: 100,000 MAU まで含む
- SMS は従量(Twilio 等の請求)
失敗パターン
| 症状 | 原因 |
|---|---|
| OAuth 後にユーザがログインできない | Redirect URL の登録ミス |
| auth.uid() が null | RLS で to authenticated が抜けている / セッション無し |
| token 期限切れ | refresh する仕組みが無い → middleware で getUser を呼ぶ |
| Email Confirmation 来ない | SMTP 設定 / レート制限 / 迷惑メール |
| onAuthStateChange が複数回 | 1 リスナで足りる、unsubscribe を忘れずに |
Auth Helpers / SSR
- @supabase/ssr: Next.js App Router / Remix / SvelteKit などのサーバ側で Cookie ベース管理
- 古い
@supabase/auth-helpers-nextjsは非推奨
Supabase Auth は「どのユーザがアクセスしているか」を JWT で判別する仕組み。 ここに RLS が組み合わさることでサーバレス的に安全な認可が実現される。 Auth + RLS はセットで覚える。