SupabaseのINSERT/UPSERTで403エラーが出た話(RLSポリシー不足の解決方法)

問題:Supabaseで403(Forbidden)になりINSERTできなかった

Supabase(supabase-js)からテーブルにupsertしようとしたところ、HTTP 403(Forbidden)となり登録できなかった。

ブラウザのネットワークタブでは次のようにPOST /rest/v1/...が403で失敗していた。

POST https://<project>.supabase.co/rest/v1/public.onidazo?on_conflict=... 403 (Forbidden)

レスポンスボディは以下の通り。

{
  "code": "42501",
  "details": null,
  "hint": null,
  "message": "new row violates row-level security policy for table \"onidazo\""
}

この記事では、このエラーの原因と解決方法を紹介する。

環境

原因:RLSポリシーが不足していた

このエラーは、テーブルに RLS(Row Level Security / 行レベルセキュリティ) が有効になっている状態で、実行ロール(anon / authenticated など)に対してINSERT(またはUPSERTに伴うUPDATE)を許可するポリシーが存在しない、またはWITH CHECK条件を満たしていないときに発生する。

今回のポイントは次の通り。

つまり、認証が無いのではなく、「そのユーザーで書き込んでよい」というルールがDB側に定義されていないことが原因だった

実装手順

1. 現状のRLSポリシーを確認

まず、SupabaseのSQL Editorpg_policiesを見ると、そのテーブルに設定されているポリシー一覧と条件(USING / WITH CHECK)を確認できる。

select
  schemaname,
  tablename,
  policyname,
  roles,
  cmd,
  qual as using_expression,
  with_check as with_check_expression
from pg_policies
where schemaname = 'public'
  and tablename  = 'onidazo'
order by policyname;

実行結果:

schemanametablenamepolicynamerolescmdusing_expressionwith_check_expression
publiconidazoonidazo_select{authenticated}SELECTtruenull
publiconidazomigration_sync_all_rw{migration_sync}ALLtruetrue

この結果から、authenticatedロールにはSELECTしか許可されておらず、INSERTUPDATEのポリシーが存在しないことが分かった。

ALL{migration_sync}ロール専用(to migration_sync)で、SELECT/INSERT/UPDATE/DELETE すべての操作を許可するポリシーである。

重要: RLSポリシーはロールごとに設定される。フロントエンド(supabase-js)からのアクセスはanon(未認証)またはauthenticated(認証済み)ロールで実行されるため、migration_syncロールのALLポリシーは適用されない。

そのため、authenticated向けのINSERTUPDATEポリシーが無い状態では、フロントからの登録は403になる。

ロールとは?

ロール(Role) は、PostgreSQLにおける「誰がアクセスしているか」を識別するための権限グループのこと。Supabaseでは以下のロールが自動的に用意されている:

RLSポリシーは「どのロールに対して、どの操作を許可するか」を定義するため、フロントエンドから呼び出す場合はanonまたはauthenticatedロール向けのポリシーが必須となる。

2. RLS(Row Level Security)について

RLS(Row Level Security)は、PostgreSQLのテーブル単位で行レベルのアクセス制御を行う仕組みである。

RLSの特徴:

ポリシーの種類:

コマンド説明使用する条件
SELECTデータの読み取りUSING
INSERTデータの挿入WITH CHECK
UPDATEデータの更新USING(既存行の参照)、WITH CHECK(更新後の検証)
DELETEデータの削除USING

USINGWITH CHECK の違い:

Supabaseでは、フロントエンドからのアクセスはanon(未認証)またはauthenticated(認証済み)ロールで実行されるため、これらのロール向けにポリシーを設定する必要がある。

3. マイグレーションでINSERT/UPDATEポリシーを追加

upsertは内部的にINSERTまたはUPDATEになるため、基本的には両方のポリシーを用意しておくのが安全である。

今回は、Supabase CLIを使ってマイグレーションファイルとして管理する方法で実装した。

3-1. マイグレーションファイルの作成

Supabase CLIでマイグレーションファイルを作成した。

supabase migration new add_onidazo_policies

このコマンドにより、supabase/migrations/ディレクトリに<timestamp>_add_onidazo_policies.sqlというファイルが作成された。

3-2. ポリシーの定義

作成されたマイグレーションファイル(例:supabase/migrations/20251217000000_add_onidazo_policies.sql)に、以下のポリシーを定義した。

-- onidazo RLS policies
-- INSERT を許可
create policy "onidazo_insert_authenticated"
on public.onidazo
for insert
to authenticated
with check (auth.uid() is not null);
 
-- UPDATE を許可(UPSERT の更新分)
create policy "onidazo_update_authenticated"
on public.onidazo
for update
to authenticated
using (auth.uid() is not null)
with check (auth.uid() is not null);

ポリシーの内容:

auth.uid()はSupabaseが提供する関数で、JWTトークンからユーザーIDを取得する。ログイン済みであればnullにならない。

3-3. マイグレーションの適用

マイグレーションファイルを作成後、以下のコマンドでデータベースに適用した。

supabase db push

これにより、本番環境(またはリモート環境)のデータベースに新しいRLSポリシーが追加された。

3-4. ポリシーの追加を確認

マイグレーション適用後、再度pg_policiesを確認して、ポリシーが正しく追加されていることを確認した。

select
  schemaname,
  tablename,
  policyname,
  roles,
  cmd,
  qual as using_expression,
  with_check as with_check_expression
from pg_policies
where schemaname = 'public'
  and tablename  = 'onidazo'
order by policyname;

実行結果:

schemanametablenamepolicynamerolescmdusing_expressionwith_check_expression
publiconidazoonidazo_insert_authenticated{authenticated}INSERTnulltrue
publiconidazoonidazo_select{authenticated}SELECTtruenull
publiconidazoonidazo_update_authenticated{authenticated}UPDATEtruetrue
publiconidazomigration_sync_all_rw{migration_sync}ALLtruetrue

onidazo_insert_authenticatedonidazo_update_authenticatedが追加され、authenticatedロールでINSERTUPDATEが可能になったことを確認した。

動作確認

ポリシーを追加後、フロントエンドから再度upsertを実行したところ、正常に登録できるようになった

ブラウザのネットワークタブでPOST /rest/v1/public.onidazo201 Createdまたは200 OKで成功することを確認した。

まとめ

参考リンク