Supabase AuthはJWTベースの認証システムで、メール/パスワードからSocial Login・MFA・企業向けSSOまで一通り対応している。Next.jsでは@supabase/ssrを使いCookieでセッションを管理するのが基本。
auth.uid()でRLSと連動するgetUser() はサーバー検証あり・getSession() はキャッシュ参照のみ。セキュリティ処理は必ず getUser() を使うSupabaseはGoTrueという認証OSSを使い、ログイン成功時にJWTを発行する。このJWTがRLSと連動する。
ログインするとSupabaseはAccess Token(JWT)とRefresh Tokenを発行する。
Access Tokenは短命(デフォルト1時間)で、Refresh Tokenで自動更新される。
JWTのsubフィールドにユーザーIDが入っており、auth.uid()はそれを取り出しているだけ。
ログイン
↓ GoTrueがJWTを発行
Access Token(JWT、1時間有効)+ Refresh Token(長期)
↓ 以降のリクエストに自動付与
PostgreSQL側で auth.uid() が JWT の sub を取り出す
↓
RLSポリシーで照合 → 行のアクセス制御
JWTのペイロードに含まれる主なクレーム:
| クレーム | 内容 |
|---|---|
sub | ユーザーID(UUID)。auth.uid()が返す値 |
role | anon(未ログイン)or authenticated(ログイン済み) |
email | ユーザーのメールアドレス |
exp | トークン有効期限(Unix時刻) |
app_metadata | ロールなどの管理者が設定するメタデータ |
user_metadata | ユーザー自身が設定できるメタデータ |
💡 ポイント:
anon keyでアクセスするとJWTのroleはanon、ログイン後はauthenticatedになる。RLSポリシーでTO authenticatedと指定することで、未ログインユーザーを弾ける。
Supabase Authは6種類の認証手法を提供しており、組み合わせて使える。
| 手法 | 概要 | 主なユースケース |
|---|---|---|
| メール/パスワード | 最も基本的な認証 | 一般的なWebアプリ |
| Magic Link / OTP | パスワードなし、メール or SMS でワンタイムURL/コード | パスワードレス認証 |
| Social Login(OAuth) | Google・GitHub・Twitter等 | ソーシャルログイン |
| 電話番号 + SMS OTP | SMS認証 | モバイル向け |
| MFA(多要素認証) | TOTP(認証アプリ)or SMS | セキュリティ強化 |
| Enterprise SSO(SAML 2.0) | Okta・Azure AD・Google Workspace等のIdP連携 | B2B・社内ツール |
サインアップ・ログイン・ログアウトの3つのメソッドを押さえれば基本は動く。
// サインアップ(確認メールが送られる)
const { data, error } = await supabase.auth.signUp({
email: 'user@example.com',
password: 'password',
})
// ログイン
const { data, error } = await supabase.auth.signInWithPassword({
email: 'user@example.com',
password: 'password',
})
// ログアウト
await supabase.auth.signOut()
// 現在のユーザーを取得(サーバー検証あり・推奨)
const { data: { user } } = await supabase.auth.getUser()
// セッションを取得(キャッシュ参照のみ・セキュリティ処理に使ってはいけない)
const { data: { session } } = await supabase.auth.getSession()
💡 ポイント:
getUser()はSupabaseサーバーにリクエストを送りJWTを検証するため、改ざんを検知できる。getSession()はCookieの値をそのまま返すだけ。認証チェックやアクセス制御には必ずgetUser()を使う。
ダッシュボードの Authentication > Email Templates でサインアップ確認メールをカスタマイズできる。
開発中は Authentication > Settings > Email で「Confirm email」をOFFにすると確認なしでサインアップが通る。
Google・GitHub等のソーシャルログインはダッシュボードでプロバイダをONにするだけで使える。
// Googleでログイン(リダイレクト方式)
await supabase.auth.signInWithOAuth({
provider: 'google',
options: {
redirectTo: 'https://example.com/auth/callback',
},
})
// GitHubでログイン
await supabase.auth.signInWithOAuth({
provider: 'github',
})
主要プロバイダと設定に必要なもの:
| プロバイダ | 必要な設定 |
|---|---|
| Google Cloud Console でOAuthクライアントID・シークレット | |
| GitHub | GitHub Developer Settings でOAuth App登録 |
| Twitter/X | Twitter Developer Portal でAppキー |
| Microsoft | Azure AD でアプリ登録 |
| Apple | Apple Developer でSign in with Apple設定 |
💡 ポイント:
redirectToにはSupabaseダッシュボードのAuthentication > URL Configuration > Redirect URLsに登録したURLを指定する。未登録のURLはリダイレクトをブロックされる。
MFAはTOTP(認証アプリ)とSMS(電話番号)の2方式をサポート。全プランで利用可能。
MFAを有効にすると、ログイン後に第2認証ステップが追加される。 ユーザーがMFAを「登録(Enroll)」してから「検証(Verify)」するという2段階のフローが必要。
// ① MFAファクターの登録開始(QRコードを表示する)
const { data, error } = await supabase.auth.mfa.enroll({
factorType: 'totp',
friendlyName: 'My Authenticator App',
})
// data.totp.qr_code → QRコードのSVG
// data.totp.secret → 手動入力用のシークレットキー
// ② ユーザーがアプリで表示されたコードを入力 → チャレンジ作成
const { data: challenge } = await supabase.auth.mfa.challenge({
factorId: data.id,
})
// ③ コードを検証してMFA登録を完了
const { data: verified } = await supabase.auth.mfa.verify({
factorId: data.id,
challengeId: challenge.id,
code: '123456', // ユーザーが入力した6桁コード
})
// 通常のログイン後、MFAが必要かチェック
const { data: { session } } = await supabase.auth.getSession()
if (session?.user.factors && session.user.factors.length > 0) {
// MFA検証が必要
const { data: challenge } = await supabase.auth.mfa.challenge({
factorId: session.user.factors[0].id,
})
// ユーザーにコードを入力させて検証
await supabase.auth.mfa.verify({
factorId: session.user.factors[0].id,
challengeId: challenge.id,
code: userInputCode,
})
}
💡 ポイント: リカバリーコードは非対応。代わりに複数のMFAファクターを登録することで代替できる(最大10個)。2つ目のファクターをリカバリー用に使うのが推奨パターン。
SAML 2.0対応のIdPとSSOを組める。アプリのユーザー向けSSO(Proプラン以上)と、Supabaseダッシュボード自体のSSO(Teamプラン以上)の2種類がある。
| 種類 | 対象 | 必要プラン | 用途 |
|---|---|---|---|
| アプリのユーザー向けSSO | 自分が作るアプリのエンドユーザー | Pro以上 | 顧客企業のOkta・Azure ADでログイン |
| ダッシュボードSSO | Supabaseダッシュボード自体 | Team以上 | 自社メンバーのダッシュボードアクセスをSSOで管理 |
対応IdP:Okta、Azure Active Directory、Google Workspace、OneLogin、JumpCloud など主要IdPに対応。
# Supabase CLIでSAML IdPを登録(v1.46.4以上が必要)
supabase sso add \
--project-ref <project-ref> \
--type saml \
--metadata-url https://your-idp.com/saml/metadata
# 登録済みIdP一覧を確認
supabase sso list --project-ref <project-ref>
// SSOでのサインイン(メールドメインで自動的にIdPを判別)
await supabase.auth.signInWithSSO({
domain: 'company.com', // そのドメインに紐付いたIdPを使用
})
// IdPのUUIDを直接指定する場合
await supabase.auth.signInWithSSO({
providerId: 'your-idp-uuid',
})
料金:$0.015 / SSO MAU(プランの無料枠を超えた分のみ)
💡 ポイント: SSOユーザーと通常ユーザーは自動リンクされない。同じメールアドレスでも別アカウントとして扱われる。SSOを後から導入する場合は既存ユーザーの移行計画が必要。
自分のSupabaseプロジェクトをOAuth 2.1の認証サーバーとして機能させる比較的新しい機能。AIエージェント(MCP)との連携や、他アプリへの「〇〇でログイン」機能を実現できる。
| ユースケース | 説明 |
|---|---|
| MCP認証 | AIエージェントをSupabaseユーザーとして認証し、RLSを適用した状態でDB操作させる |
| 「〇〇でサインイン」 | 自分のアプリをGoogleやGitHubのような認証プロバイダにする |
| 企業内SSO | 社内ツール間でSupabaseを認証ハブとして使う |
| サードパーティAPI連携 | スコープ付きアクセストークンを発行して外部に安全に公開 |
ダッシュボードの Authentication > OAuth Server からONにする。
OAuth 2.1・OpenID Connect・PKCE・Dynamic Client Registrationに対応。
Supabase Auth(OAuth 2.1 Server)
↓ Authorization Code Flow with PKCE
MCPクライアント / サードパーティアプリ
↓ Access Token(通常のSupabase JWT)
Supabase DB(RLSが自動適用)
💡 ポイント: Access TokenはSupabaseの通常JWTと同じ形式。既存のRLSポリシーがそのままAIエージェントのアクセスにも適用される。MCPサーバー側に別途アクセス制御ロジックを書く必要がない。
Next.js App Router では Server / Client でクライアントを分け、Middleware でセッションを常に更新する設計が必要。
// lib/supabase/client.ts(Client Components用)
import { createBrowserClient } from '@supabase/ssr'
export const createClient = () =>
createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)
// lib/supabase/server.ts(Server Components・Route Handler用)
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'
export const createClient = async () => {
const cookieStore = await cookies()
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)
)
},
},
}
)
}
| 場所 | クライアント | セッション管理 |
|---|---|---|
| Client Components | createBrowserClient | LocalStorage / Cookie |
| Server Components | createServerClient | Cookie |
| Route Handlers | createServerClient | Cookie |
| Middleware | createServerClient | Cookie |
// middleware.ts
import { createServerClient } from '@supabase/ssr'
import { NextResponse, type NextRequest } from 'next/server'
export async function middleware(request: NextRequest) {
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)
)
},
},
}
)
// ⚠️ getUser()を必ず呼ぶ。これがセッション更新をトリガーする
const { data: { user } } = await supabase.auth.getUser()
// 未認証ユーザーをリダイレクト
if (!user && request.nextUrl.pathname.startsWith('/dashboard')) {
const url = request.nextUrl.clone()
url.pathname = '/login'
return NextResponse.redirect(url)
}
return supabaseResponse // ← 必ずこれを返す。新しいNextResponse.next()を返すとCookieが失われる
}
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
}
💡 ポイント: Middlewareで
getUser()を呼ばないとセッションが期限切れのまま更新されない。最後にsupabaseResponseを返すのも必須で、新しいNextResponse.next()を返すとCookieのセットが失われる。
| ミス | 影響 | 対策 |
|---|---|---|
セキュリティチェックにgetSession()を使う | キャッシュ値を返すため改ざんトークンを見抜けない | 認証チェックは必ずgetUser() |
MiddlewareでsupabaseResponse以外を返す | CookieのセットがブロックされセッションがServerで取れなくなる | 必ずsupabaseResponseを返す |
SERVICE_ROLE_KEYにNEXT_PUBLIC_をつける | フロントに漏洩するとRLSをバイパスされ全データ危険 | NEXT_PUBLIC_はURLとANON_KEYだけ |
| SSOユーザーと通常ユーザーの自動リンクを期待する | 同じメールでも別アカウントになる | SSO導入時は移行計画が必要 |
| MFAでリカバリーコードを前提にする | Supabaseはリカバリーコード非対応 | 2つ目のMFAファクターをリカバリー用に使う |
redirectToを未登録URLにする | Social LoginのOAuthコールバックがブロックされる | Redirect URLsに本番・開発URLを両方登録 |