従来の開発スタイルでは、APIサーバー側で「このユーザーはこのデータを閲覧できるか」をIF文で制御していましたが、Supabase(BaaS)ではその役割を データベース自身 が担います。この記事では、RLSの仕組みから、実務で安心して使えるポリシー設計のベストプラクティス、そしてパフォーマンス最適化までを徹底解説します。
1. RLS(行レベルセキュリティ)の本質#
RLSは、PostgreSQLのテーブルに対して 「特定の条件を満たす行(レコード)だけを可視化・操作可能にする」 セキュリティ機能です。
なぜ「APIサーバー」ではなく「DB」で守るのか?#

Supabaseの最大の特徴は、フロントエンド(Next.js等)から直接データベースへクエリを投げられる点にあります。ここでAPIサーバーによる仲介がないため、「誰でもすべてのデータが見えてしまうのではないか?」という懸念が生じます。
RLSは、そのクエリが実行される データベース層の直前 で動作します。
- ユーザーがリクエストを送信。
- PostgreSQLが「このユーザーに許可されたデータはどれか?」をRLSポリシーに照らし合わせる。
- 許可された行だけ を取得対象として扱い、他は存在しないものとして無視される。
つまり、開発者が誤って「全件取得」のクエリをフロントエンドで書いてしまっても、RLSが有効であれば「ログイン中の本人のデータ」しか返ってこないのです。これが「最後の防壁」と呼ばれる理由です。
2. RLS導入の基本:USING と WITH CHECK の違い#
ポリシーを定義する際、USING と WITH CHECK の使い分けが混乱の元になります。
USING(参照制御):- 既存の行を 読み取る(SELECT) または 削除する(DELETE) 際に適用される条件。
- 条件に合わない行は、SELECTクエリでは「存在しないもの」として扱われ、DELETEでは無視されます。
WITH CHECK(更新制御):- 新しく 作成する(INSERT) または 更新する(UPDATE) 後のデータが満たすべき条件。
- 条件を満たさないデータを保存しようとすると、エラーが発生します。
| 操作 | 適用される条件 |
|---|---|
| SELECT | USING |
| INSERT | WITH CHECK |
| UPDATE | USING (更新前) + WITH CHECK (更新後) |
| DELETE | USING |
3. 実務で多用するポリシー設計パターン#
パターンA:自分自身のデータのみ管理#
-- 読み取り
CREATE POLICY "Users can view their own profiles"
ON profiles FOR SELECT
TO authenticated
USING ( auth.uid() = id );
-- 追加
CREATE POLICY "Users can create their own profile"
ON profiles FOR INSERT
TO authenticated
WITH CHECK ( auth.uid() = id );パターンB:公開・非公開フラグによる制御#
-- 公開記事は全員、非公開は本人のみ
CREATE POLICY "Anyone can view public posts, authors can view private"
ON posts FOR SELECT
USING ( is_public = true OR auth.uid() = author_id );4. 【中級編】パフォーマンスを落とさない設計#
RLSはクエリのたびに評価されるため、複雑な結合(JOIN)を含むポリシーはパフォーマンスを著しく低下させます。
❌ アンチパターン:ポリシー内でのサブクエリ#
-- データの件数が増えるほど指数関数的に遅くなる可能性がある
USING (
EXISTS (
SELECT 1 FROM team_members
WHERE team_id = posts.team_id AND user_id = auth.uid()
)
);✅ 解決策1:Security Definer関数の利用#
複雑な条件判定は、PostgreSQLの関数として定義し、その関数の結果をポリシーで利用するのが定石です。SECURITY DEFINER を使うことで、一時的に高い権限でチェックを実行し、結果だけを返せます。
✅ 解決策2:JWT(カスタムクレーム)の活用#
特定のユーザーが「管理者(admin)」かどうかを判定する場合、毎回DBを引くのではなく、Supabase AuthのJWTに is_admin などのフラグを含めておき、auth.jwt() ->> 'is_admin' で参照すると高速です。
5. 重要:service_role キーと RLS の関係#
Supabaseには、全てのRLSをバイパスできる強力な service_role キーが存在します。
- クライアント(ブラウザ): 必ず
anonキーを使い、RLSを有効にする。 - Edge Functions / サーバーサイド:
service_roleキーを使い、複雑なバリデーションや管理者操作を行う。
service_role キーを決してフロントエンドのコード(.env.local 等)に含めないでください。漏洩した場合、データベース内の全てのデータが読み書き可能になってしまいます。
まとめ:RLSは「後付け」できない#
Supabaseでの開発において、セキュリティの主役は常にデータベース(RLS)であるべきです。
- テーブルを作ったら即座に
ENABLE RLS。 - 「Default Deny(デフォルト拒否)」 の原則に従い、許可するものだけを足していく。
USINGとWITH CHECKの違いを正しく理解し、更新時のバリデーションを怠らない。
このフローを徹底するだけで、あなたのサービスは「堅牢な要塞」へと変わります。「API側で守っているから大丈夫」という慢心を捨て、データベース自身に自身のセキュリティを守らせる──これこそが、モダンなBaaS開発における正解です。











