Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【ITニュース解説】Your Supabase Auth Is Only as Good as Your RLS Policies. So Test Them.

2026年10月07日に「Dev.to」が公開したITニュース「Your Supabase Auth Is Only as Good as Your RLS Policies. So Test Them.」について初心者にもわかりやすく解説しています。

作成日: 更新日:

ITニュース概要

Supabaseでデータ保護を確実にするには、認証だけでなくRLSポリシーが不可欠だ。RLSの誤設定はエラーにならずデータ漏洩を招くため、手動テストでは見逃しやすい。異なるユーザーが互いのデータにアクセスできないか、所有者がアクセスできるかを検証する統合テストを必ず実施しよう。

ITニュース解説

Supabase(スーパーベース)を利用したアプリケーション開発では、データのセキュリティを確保するために、認証(Auth)と行レベルセキュリティ(RLS: Row Level Security)という二つの重要な仕組みを適切に理解し、運用する必要がある。認証は「あなたが誰であるか」をシステムに伝える役割を果たすが、RLSは「そのユーザーがどのデータにアクセスできるか」を厳密に制御する。このRLSの機能が正しく設定されていない場合、認証が成功していても、意図しないデータ漏洩が発生するリスクがある。

RLSポリシーが適切に機能しないと、深刻なデータ漏洩につながる可能性がある。さらに問題なのは、RLSの失敗が多くの場合、エラーとして通知されないことだ。例えば、あるユーザーが本来アクセスできないはずのデータを取得しようとした際、システムはエラーメッセージを返す代わりに、単に空のデータ([])を返す。このため、開発者がアプリケーションを手動で操作して動作確認をするだけでは、RLSポリシーの不備を見つけることが非常に難しい。ユーザーからは単にデータが表示されないように見えるため、システムが正しく動作しているように錯覚してしまうのだ。

実際に起こりうるデータ漏洩のシナリオとして、次のようなケースが考えられる。アプリケーションの公開後、ログイン機能は正常に動作し、セッションも維持される。しかし、悪意のあるユーザーがWebブラウザの開発者ツールなどを使い、公開されている匿名キー(anon key)を取得し、直接データベースに対して「全てのノートを取得する」というリクエストを実行する。もし、そのテーブルでRLSが有効化されていなければ、データベースに存在する全てのユーザーのノートが返されてしまう。anon keyは設計上公開されるものであるため、データベースとanon keyの間に立つ唯一の防御壁がRLSポリシーなのだ。認証はあくまで「誰がリクエストをしているか」をPostgreSQLに伝えるだけであり、そのユーザーが特定の行に触れることを許可するかどうかは、開発者が書いたRLSポリシーによってのみ決定される。もしポリシーが正しく書かれていなければ、そのリクエストを阻止するものは何も存在しない。

例として、各ユーザーが自分のノートのみを閲覧・操作できるようなシンプルなnotesテーブルを保護するケースを考えてみよう。このテーブルには、ノートID、ユーザーID、ノート本文、作成日時が含まれる。RLSを有効にするには、まずテーブルに対してalter table public.notes enable row level security;というSQL文を実行する必要がある。その後、特定の操作(SELECT, INSERT, UPDATE, DELETE)に対して、auth.uid()関数を使って現在の認証済みユーザーのIDと、ノートのuser_idが一致する場合にのみ操作を許可するポリシーを記述する。例えば、自分のノートを読み取るポリシーはusing ((select auth.uid()) = user_id);となる。auth.uid()を直接使う代わりに(select auth.uid())と書くのは、大規模なテーブルにおいてPostgreSQLが関数をクエリごとに一度だけ評価し、パフォーマンスを向上させるための工夫だ。また、ノートを作成する際にuser_idにdefault auth.uid()を設定しておけば、クライアントがuser_idを送信する必要がなくなり、悪意のあるユーザーがuser_idを偽装して他のユーザーのノートを作成しようとする攻撃を防ぐことができる。

RLSが静かに機能不全に陥る典型的な間違いはいくつか存在する。 一つ目は、RLSポリシーは作成したものの、テーブルに対してenable row level securityを適用し忘れるケースだ。ポリシー自体はデータベースに存在するが、強制的に適用されていないため、テーブルは完全に無防備な状態となる。 二つ目は、INSERTポリシーの確認内容が誤っているケースだ。例えば、create policy "insert notes" on public.notes for insert to authenticated with check (true);のように設定してしまうと、ログインしているユーザーであれば誰でも、user_idを任意の値に設定してノートを作成できてしまう。これにより、ユーザーBがユーザーAのIDでノートを作成し、コンテンツを偽装するなどの問題が発生する。 三つ目は、user_metadataを信頼してポリシーを記述するケースだ。auth.jwt()から取得できるuser_metadataは、supabase.auth.updateUser()関数を通じてユーザー自身が編集できてしまう。もしポリシーがuser_metadata内のroleがadminであるかをチェックしていると、悪意のあるユーザーは自分を管理者にしてしまうことが可能になる。ロール情報などはapp_metadataか、あるいはアプリケーション独自のテーブルで管理すべきである。 四つ目は、ビューがRLSを迂回してしまうケースだ。デフォルトでは、ビューはその所有者の権限で実行されるため、基になるテーブルのRLSポリシーが適用されない場合がある。PostgreSQL 15以降では、create views with (security_invoker = true)としてビューを作成することで、ビューを呼び出したユーザーの権限でRLSが適用されるようにできる。 これらの間違いはどれも、アプリケーションがエラーを発生させずに正常に動作しているように見えるため、データが外部に公開されていることに気づきにくいという共通の危険性を持つ。

このような問題を未然に防ぐための確実な方法は、統合テストを実施することだ。テストの基本的なアプローチは、二人の異なるユーザーを実際に登録し、それぞれが独立したクライアント(セッション)を持つ状態で、お互いのデータにアクセスしたり、変更したり、削除したりできないことを確認するというものになる。VitestのようなテストランナーとSupabaseのJavaScriptクライアントライブラリを組み合わせてテストを記述する。

具体的なテストでは、まずnewClient()関数を使って、セッションが他のクライアントと共有されない独立したSupabaseクライアントを生成する。次にsignUpUser()関数で、ランダムなメールアドレスと固定のパスワードを使って二人のテストユーザー(例えば「アリス」と「ボブ」)を登録し、それぞれのクライアントとユーザーIDを取得する。テストが始まる前に、アリスとボブそれぞれが自分のノートを一つずつ作成し、そのノートIDを記録しておく。

そして、以下のようなテストシナリオを実行する。ボブがアリスのノートを読み取ろうとした際にデータが返されないこと、更新や削除ができないこと、アリスのuser_idを指定して新しいノートを挿入できないこと、自分のノートのuser_idをアリスのものに変更できないことを確認する。匿名ユーザーがテーブルのデータを読み取ろうとした際に何もデータが返されないことも確認する。最も重要なテストとして、アリスが自分のノートを問題なく読み取れることも確認する。この最後のテストは、RLSポリシーが厳しすぎて誰もデータにアクセスできなくなっていないかを検証するために不可欠だ。

これらのテストを実行するには、実際の認証サーバーとPostgreSQLデータベースが必要となる。RLSはPostgreSQLの内部で実行されるため、モック(模擬)環境では正しく動作を確認できない。Supabaseの公式なローカル開発環境であるsupabase startは全てのSupabaseスタックをDockerコンテナで起動するが、テストスイートにはオーバーヘッドが大きく、CI/CD環境での実行が遅くなる可能性がある。このような場合、tinbaseのようなSupabase互換の軽量バックエンドが有効だ。tinbaseはDockerを使わずに単一プロセスで実行でき、既存のSupabaseマイグレーションを読み込み、RLSと認証機能をサポートするため、テストを数秒で実行できる。CI/CDパイプラインにも簡単に組み込むことが可能で、GitHub Actionsのワークフロー例が示すように、tinbase startでバックエンドを起動し、vitest runでテストを実行する流れとなる。

結論として、データを安全に保護するためには、新しいテーブルでRLSを有効にするたびに、少なくとも二つのテストを実施するべきだ。一つは「部外者がデータにアクセスできないこと」を確認するテスト、もう一つは「データの所有者が適切にデータにアクセスできること」を確認するテストである。これらはそれぞれ約10分程度で設定でき、手動テストでは見落としがちなセキュリティ上の欠陥を確実に発見し、データ漏洩のリスクを大幅に軽減することに役立つ。これにより、アプリケーションが意図しないセキュリティホールを持つことなく、安心してリリースできるようになる。

関連コンテンツ

関連IT用語

関連ITニュース