はじめに
Supabase の Database Webhooks を使用して、Stripe で発行されたidを Supabaseの profileテーブルのstripe_customerカラムを更新する際に、RLS(Row Level Security)ポリシーによって 更新が拒否される問題 が発生しました。
この記事では、この問題の原因と解決方法を説明します。
この問題は、Shin Code さんの Udemy 講座「ご自身の Web サービスで Stripe 決済システムを導入してマネタイズしたい方へ。レッスン講座販売アプリを作りながら Stripe でサブスク決済システムを構築します。同時に Next.js と Supabase のコアな理解も深めることが可能です。」で学習中に遭遇した内容を基にしています。
開発環境
- Next.js: 16.1.0
- React: 19.2.3
- @supabase/ssr: 0.8.0
- @supabase/supabase-js: 2.89.0
- stripe: 20.1.0
- TypeScript: 5.x
問題の原因
Webhook からのリクエストには認証クッキーが含まれていないため、createServerClient()で作成した Supabase クライアントは認証情報を持ちません。
その結果、RLS ポリシーで auth.uid() を使用した条件が満たされず、更新が拒否されていました。
createServerClient()
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_PUBLISHABLE_KEY!,
{
cookies: {
getAll() {
return cookieStore.getAll()
},
setAll(cookiesToSet) {
try {
cookiesToSet.forEach(({ name, value, options }) => cookieStore.set(name, value, options))
} catch {
// The `setAll` method was called from a Server Component.
// This can be ignored if you have middleware refreshing
// user sessions.
}
},
},
}
)
}
Webhook と Trigger の違い:
- Trigger(データベーストリガー)
データベース内で直接 SQL を実行する機能- Webhook
データベースの変更を外部の API エンドポイント(Next.js の Route Handler など)に通知する機能。データベース外の処理(Stripe 顧客作成など)を実行する際に使用する
問題のあったポリシー
このポリシーでは、認証済みユーザーが自分のレコード(auth.uid() = id)のみ更新可能でした。しかし、Webhook からのリクエストには認証情報がないため、auth.uid() が NULL となり、条件を満たさず更新が拒否されていました。
ALTER POLICY "enable_update_profile_customer_id"
ON "public"."profile"
TO authenticated
USING (
auth.uid() = id
);
解決方法
1. ポリシーの設定
profileテーブルの UPDATE ポリシーを以下のように設定します。
ALTER POLICY "enable_update_profile_customer_id"
ON "public"."profile"
to public
USING (
auth.uid() IS NULL
AND
stripe_customer IS NULL
)
WITH CHECK (
stripe_customer IS NOT NULL
);
2. ポリシーの動作
-
USING句
auth.uid() IS NULLで認証なし
更新前の行がstripe_customer IS NULLの場合のみ更新を許可 -
WITH CHECK句
更新後の行がstripe_customer IS NOT NULLであることを確認 -
TO public
認証の有無に関わらず、条件を満たせばアクセス可能
3. セキュリティ上の利点
-
stripe_customerがNULLの時のみ更新可能(初回設定時のみ) - 一度設定された値は誤って上書きされることを防止
-
auth.uid() IS NULLにより、Webhook からのリクエストでも動作
さいごに
今回は Webhook を初めて使ったこともあり、問題の特定に時間がかかりました。
また記載した内容で問題は解決しましたが、セキュリティ的にはまだ問題はあると思うので、他に適切な対応方法があれば是非教えてください。