---
title: "Supabase RLSを有効にする方法と結果"
description: "SupabaseでRLSを有効にする方法を、ダッシュボードとSQLで解説。ローカル環境で、公開キー、2人のユーザー、シークレットキーに何が返るか検証しました。"
url: "https://madsalt.tech/ja/blog/how-to-enable-rls-in-supabase/"
author: "Denys Zaichenko"
author_url: "https://madsalt.tech/ja/about/"
language: "ja"
published: 2026-11-04
---

# Supabase RLSを有効にする方法と結果

TL;DR

SupabaseでRLSを有効にするには、テーブルごとに文を1つ実行します。`alter table public.todos enable row level security;`、またはダッシュボードの「Enable RLS」です。SQLエディタやマイグレーションで作ったテーブルは、デフォルトでは有効になりません。有効にした直後、テーブルはエラーを返すのではなく、静かになります。ローカルでの私のテストでは、public keyと、ログイン済みの2人のユーザーは、select、update、deleteで`200 []`を受け取り、失敗したのはinsertだけで`42501`でした。secret keyは3行すべてを見たままでした。テーブルの上に通常のビューを作ると、public keyに3行すべてが渡ったままでした。2026年10月30日からは、新しいテーブルは何かが届く前にgrantも必要になるため、grantと同じマイグレーションでRLSを有効にし、ポリシーを書いてください。

私は無料の[Supabaseセキュリティチェッカー](https://madsalt.tech/ja/tools/supabase-security-check/)を運営しています。その中で「RLS disabled」は、最も説明が不十分だと感じる指摘です。多くのガイドは、RLSを有効にすると「テーブルにアクセスできなくなる」と書いています。読み取りについては正しく、書き込みについては誤解を招きます。そこで、ローカルのSupabaseスタックに1つのテーブルを作り、下の各ステップのあとに、public key、ログイン済みの2人のユーザー、secret keyとして、あらゆる種類のリクエストを送りました。

## SupabaseのRLSとは？

RLS（row-level security、行レベルセキュリティ）は、リクエストごとに行を絞り込むPostgresの機能です。Supabaseは`supabase-js`からのすべてのリクエストを、ロールとして実行されるデータベースクエリに変換します。public（anon、publishable）keyなら`anon`、ログイン済みのユーザーなら`authenticated`です。RLSが有効だと、Postgresは行ごとにポリシーを確認し、ポリシーが許可した行だけを返し、変更し、削除します。RLSが無効だと、テーブルにgrantを持つロールは、すべての行に届きます。

secret key（service role）は別です。そのロールは`BYPASSRLS`を持つため、RLSは決して適用されません。サーバー上にだけ置くべき理由です。2つのキーの違いは、[Supabase anon keyとservice role keyの違い：何が開くか](https://madsalt.tech/ja/blog/supabase-anon-key-vs-service-role-key/)にあります。

## SupabaseでRLSを有効にするには？

テーブルの作り方によって、3つの方法があります。

- **Table Editorでテーブルを作るとき**は、「Enable Row Level Security (RLS)」にチェックを入れたままにします。Supabaseの[ダッシュボードのソース](https://github.com/supabase/supabase/commit/33bd798d142183e162c25debcc50c8c711e68f66)では「Recommended」と表示され、外すと、テーブルが誰にでも読み書きできる状態になるという警告が出ます。
- **既存のテーブル**については、Supabaseの[APIセキュリティのガイド](https://supabase.com/docs/guides/api/securing-your-api)に、Database、Policiesの順に進み、テーブルの「Enable RLS」を選ぶとあります。
- **SQLで**、SQLエディタまたはマイグレーションに書きます。

```sql
alter table public.todos enable row level security;
```

Supabaseのガイドは明確に述べています。ダッシュボードで作ったテーブルはデフォルトでRLSが有効で、SQLエディタや別のツールで作ったテーブルはそうではありません。マイグレーションやAIツールでスキーマを作っているなら、すべてのテーブルを確認してください。

## RLSを有効にした直後はどうなる？

insert以外はエラーになりません。所有者の列（`user_id uuid default auth.uid()`）を持つ`public.todos`を作り、古いプロジェクトと同じようにAPIのロールにgrantして、3行を追加しました。ユーザーAの2行とユーザーBの1行です。そのあと、ポリシーなしでRLSを有効にしました。

| リクエスト       | public key        | ユーザーA         | ユーザーB         | secret key    |
| ---------------- | ----------------- | ----------------- | ----------------- | ------------- |
| Select           | 200、0行          | 200、0行          | 200、0行          | 200、3行      |
| 自分の行のInsert | 401、エラー 42501 | 403、エラー 42501 | 403、エラー 42501 | 201、行を挿入 |
| 行のUpdate       | 200、0行          | 200、0行          | 200、0行          | 200、1行      |
| 行のDelete       | 200、0行          | 200、0行          | 200、0行          | 200、1行      |

insertのエラーは`new row violates row-level security policy`です。updateとdeleteは空の配列を付けて`200`を返すため、返された行を確認しないアプリは、何も保存されていないのに「保存しました」と報告します。RLSを有効にしたあとは、書き込んだ内容を読み戻すか、`Prefer: count=exact`で件数を取得してください。

RLSの前は、同じgrantのままで、public keyは3行すべてを読み、insertでき、任意の行をupdateやdeleteできました。ユーザーAはユーザーBの行を編集・削除でき、自分の行をBに渡すこともできました。

## テーブルをまた使えるようにするには？

操作ごとにポリシーを書きます。自分の行用の一般的なセットは次のとおりです。

```sql
create policy "own select" on public.todos for select to authenticated
  using ((select auth.uid()) = user_id);
create policy "own insert" on public.todos for insert to authenticated
  with check ((select auth.uid()) = user_id);
create policy "own update" on public.todos for update to authenticated
  using ((select auth.uid()) = user_id) with check ((select auth.uid()) = user_id);
create policy "own delete" on public.todos for delete to authenticated
  using ((select auth.uid()) = user_id);
```

これで、ユーザーAは自分の2行、ユーザーBは自分の1行が見えました。public keyは0行のままです。AがBの所有する行をinsertしようとしたとき、または自分の行をBに移そうとしたときの応答は`403, 42501`でした。この拒否は、updateの`with check`によるものです。AがBの行をupdateまたはdeleteしようとしたときの応答は`200, 0 rows`でした。ほかのパターン（公開プロフィール、チーム、MFA）は、[Supabase RLSポリシー例：APIで検証済み](https://madsalt.tech/ja/blog/supabase-rls-policy-examples/)にあります。

secret keyについて1つ細かな点があります。secret keyが挿入した行は`user_id = null`になりました。そのキーの背後にはユーザーがおらず、`auth.uid()`が何も返さないためです。RLSは迂回されましたが、デフォルト値は列名が示唆する動きをしませんでした。

## 各ステップでpublic keyは何を読めた？

![public keyが読めた行数（3行中）。3行のSupabaseテーブルを、各ステップのあとに確認。新規、grantなし、RLS無効（42501）：0、grantあり、RLS無効：3、RLS有効、ポリシーなし：0、RLS有効、自分の行のポリシー：0、通常のビュー：3、security_invokerのビュー：0。](https://madsalt.tech/charts/ja/how-to-enable-rls-in-supabase.svg)

*図1. ローカルのSupabaseスタック（CLI 2.109.1、Postgres 17.6）。2026年10月28日。*

すべてが漏れるステップが2つあります。RLSのないgrantと、正しいポリシーを持つテーブルの上の通常のビューです。次の2つのセクションで、その両方を説明します。

## 10月30日のgrant変更のあとも、RLSは必要？

必要です。2026年10月30日から、Supabaseは`public`の新しいテーブルに、APIのロールへの自動grantを付けなくなりました。新規プロジェクトも既存プロジェクトも同じです。私のローカルスタックは、すでにその動作でした。RLSが無効の状態で作ったばかりのテーブルは、secret keyを含むすべてのロールに`42501`を返したため、何も漏れませんでした。詳細は[Supabase 10月30日のgrant変更：壊れる点と確認方法](https://madsalt.tech/ja/blog/supabase-october-30-grant-change/)にあります。

ただし、アプリがテーブルを必要とした時点でgrantは戻ってきます。そこからは、public keyとすべての行の間にあるのはRLSだけです。そこで、すべてを1つのマイグレーションに、この順番で入れてください。

```sql
create table public.todos (...);
alter table public.todos enable row level security;
create policy "own select" on public.todos ...;
grant select, insert, update, delete on table public.todos to authenticated;
```

grantは、そのテーブルが必要なロールだけに与えます。`anon`からすべてを取り消すと、public keyのselectは`200 []`から`401, 42501`に変わりました。grantが1つ目の鍵、RLSが2つ目の鍵です。

## ビューではなぜ行が漏れる？

ビューは所有者の権限で実行され、Supabaseでは所有者が`postgres`で、RLSを迂回するからです。`todos`に自分の行のポリシーを設定したうえで、`create view public.todos_view as select * from public.todos`という通常のビューを作ると、public keyとユーザーBに3行すべてが返りました。

`with (security_invoker = true)`で作り直すと、同じビューがテーブルとまったく同じ結果になりました。public keyには0行、Aには2行、Bには1行です。

```sql
create view public.todos_view with (security_invoker = true) as
  select * from public.todos;
```

SupabaseのAdvisorは、通常の種類を`security_definer_view`として警告します。その意味と直し方は、[Supabase security definer viewの解説ノート](https://madsalt.tech/ja/notes/supabase-security-definer-view/)にあります。

## 新しいテーブルすべてでRLSを自動的に有効にするには？

イベントトリガーを使います。Supabaseの[イベントトリガーのガイド](https://supabase.com/docs/guides/database/postgres/event-triggers)には、関数`rls_auto_enable()`と、`public`での`create table`、`create table as`、`select into`のあとに実行されるトリガー`ensure_rls`が載っています。公開されているとおりに入れたところ、次の結果になりました。

- そのあとに`create table`で作ったテーブルは、`alter`なしでRLSが有効でした。public keyはselectで`200 []`、insertで`401`でした。
- `create table as select`で作ったテーブルも、RLSが有効でした。
- トリガーより前に作ったテーブルは、無効のままでした。トリガーはさかのぼって適用されません。

トリガーがないと、保護されたテーブルの隣に作った2つ目のテーブルはRLSが無効で、grantすればpublic keyが自由に読み書きできました。また、対象は`public`だけです。公開している他のスキーマには、関数に専用の行が必要です。

## FORCE ROW LEVEL SECURITYは役に立つ？

心配なロールには効きません。`alter table ... force row level security`は、RLSをテーブルの所有者にも適用させます。Supabaseでは`postgres`も`service_role`も`BYPASSRLS`を持つため、`force`のあとも所有者は3行を数え、secret keyはすべてを見たままで、`postgres`が所有するビューは漏れたままでした。変化があったのは、`BYPASSRLS`なしで私が作った所有者ロールだけで、以前は2行、以後は0行でした。

## 自分のプロジェクトをどう確認する？

- SQLエディタで、RLSが無効な`public`のテーブルを一覧します。

```sql
select c.relname as table_name
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
where n.nspname = 'public'
  and c.relkind in ('r', 'p')
  and not c.relrowsecurity
order by 1;
```

- ダッシュボードのAdvisorは、同じ内容を`rls_disabled_in_public`として報告します。このエラーの意味は[RLS disabled in publicの解説ノート](https://madsalt.tech/ja/notes/supabase-rls-disabled-in-public/)にあります。
- 無料の[Supabaseセキュリティチェック](https://madsalt.tech/ja/tools/supabase-security-check/)は、プロジェクトを読み取り専用で調べ、APIが届くRLS無効のテーブル、RLSを迂回するビュー、全員に許可するポリシーを、それぞれの修正SQL付きで一覧します。
- スキャナーは、どの行が誰のものかを知ることができません。それを証明するには、上のように2人のユーザーとしてリクエストを送ります。ロールのあるアプリの完全な方法は、[Supabase RLSのテスト：87権限中30が無効だった](https://madsalt.tech/ja/blog/how-to-test-supabase-rls/)にあります。

## テスト方法

- ノートPC上のSupabase CLI 2.109.1：Postgres 17.6、PostgREST 14.14、Auth 2.192.0。パスワードでログインした2人のユーザーと、ローカルのpublishable keyおよびsecret key。
- テーブルは`public.todos`の1つで、リクエストのたびに同じ3行に戻しました。各ステップはSQLとして適用し、各リクエストはREST API経由で送り、ステータスコードと本文を記録しました。
- ステップ2〜5のgrantは、2026年10月30日より前に作られたプロジェクトに合わせて、手作業で追加しました。

**限界。** ローカルスタック1つ、架空のデータ、2026年10月28日です。ダッシュボードの手順はSupabaseのドキュメントからの引用で、実際にクリックして確認したものではありません。ダッシュボード自身のロールで作ったテーブルは、別のデフォルトgrantになる可能性があります。それも、Storage、Realtime、GraphQLも、テストしていません。

テーブルが守れているか不安ですか？

スキーマを一緒に見ることができれば嬉しいです。売り込みはありません。

[お問い合わせ](https://madsalt.tech/ja/#contact)
