Supabaseのデータベースのセキュリティを担う「Row Level Security(行レベルセキュリティ)」とは、その名の通り、データベースの各行に対して個別にセキュリティ設定を行う機能です。
これにより、特定の行に対するアクセスを制限し、より細かいセキュリティ管理が可能となります。
この機能を実現するために、Policy(ポリシー)と呼ばれるセキュリティルールを定義したオブジェクトを使用します。ポリシーを定義し、テーブルに適用することで、どのユーザーがどの操作を実行できるかを行単位で管理できます。
Firebaseに馴染みのある方は、Firestoreの「セキュリティルール」と同様の概念と考えていただければわかりやすいでしょう。
元々はPostgreSQLの機能ですが、Supabaseではコンソールからボタンをクリックするだけで簡単に設定できます。
【目次】
- SupabaseのRLS
- ポリシー
- 行レベルセキュリティの有効化
- 認証済みおよび未認証のロール
- ポリシーの作成
- SELECTポリシー
- INSERTポリシー
- UPDATEポリシー
- DELETEポリシー
- ビュー
- ヘルパー関数
- auth.uid()
- auth.jwt()
- MFA
- RLSのバイパス
SupabaseのRLS
細かい認証ルールが必要な場合、PostgresのRow Level Security(行レベルセキュリティ, RLS)に勝るものはありません。
RLS(Row Level Security)は非常に強力で柔軟性があり、複雑なSQLルールを作成して、独自のビジネスニーズに合わせることができます。RLSは、ブラウザからデータベースまでユーザーのセキュリティをエンドツーエンドで保護するために、Supabase Authと組み合わせることができます。
RLSはPostgresの基本機能であり、サードパーティのツールを通じてアクセスされる場合でも、悪意のあるアクターからデータを保護するための「防御の深層化」を提供します。
publicスキーマに作成されたテーブルには、常にRLSを有効にすべきです。これはTable Editorでテーブルを作成すると自動的に行われます。SQL Editorや生のSQLでテーブルを作成する場合は、RLSを自分で有効にすることを忘れないでください。
ポリシー
ポリシーはPostgresのルールエンジンです。ポリシーは一度理解すると簡単です。各ポリシーはテーブルに付属し、テーブルにアクセスされるたびに実行されます。
ポリシーは、すべてのクエリにWHERE
句を追加するようなものです。例えば、以下のようなポリシーは...
create policy "Individuals can view their own todos."
on todos for select
using ( (select auth.uid()) = user_id );
... ユーザーがtodosテーブルから選択しようとするときに以下のように変換されます:
select *
from todos
where auth.uid() = todos.user_id;
-- ポリシーが暗黙的に追加されます。
行レベルセキュリティの有効化
任意のテーブルに対してenable row level security
句を使用してRLSを有効にすることができます:
alter table "table_name" enable row level security;
RLSを有効にすると、ポリシーを作成するまでpublicのanon
キーを使用してAPI経由でデータにアクセスできなくなります。
認証済みおよび未認証のロール
Supabaseは各リクエストを以下のロールのいずれかにマッピングします:
anon
: 未認証のリクエスト(ユーザーがログインしていない)authenticated
: 認証済みのリクエスト(ユーザーがログインしている)
これらは実際にはPostgresのロールです。これらのロールは、TO
句を使用してポリシー内で使用できます:
create policy "Profiles are viewable by everyone"
on profiles for select
to authenticated, anon
using ( true );
-- または
create policy "Public profiles are viewable only by authenticated users"
on profiles for select
to authenticated
using ( true );
匿名ユーザーとanonキーの違い:
anon
Postgresロールを使用することは、Supabase Authにおける匿名ユーザーとは異なります。匿名ユーザーはデータベースにアクセスするためにauthenticated
ロールを引き受け、JWTのis_anonymous
クレームをチェックすることで永続的なユーザーと区別することができます。
ポリシーの作成
ポリシーは単にPostgresテーブルに付属するSQLロジックです。各テーブルにいくつでもポリシーを付けることができます。
Supabaseは、Supabase Authを使用している場合にRLSを簡単にするためのいくつかのヘルパーを提供しています。これらのヘルパーを使用して基本的なポリシーを説明します:
SELECTポリシー
using
句を使用してselectポリシーを指定できます。
例えば、publicスキーマにprofiles
というテーブルがあり、誰でも読み取れるようにしたいとします。
-- 1. テーブルを作成
create table profiles (
id uuid primary key,
user_id references auth.users,
avatar_url text
);
-- 2. RLSを有効にする
alter table profiles enable row level security;
-- 3. ポリシーを作成
create policy "Public profiles are visible to everyone."
on profiles for select
to anon -- 推奨されるPostgresロール
using ( true ); -- 実際のポリシー
あるいは、ユーザーが自分のプロファイルだけを見られるようにしたい場合:
create policy "User can see their own profile only."
on profiles
for select using ( (select auth.uid()) = user_id );
INSERTポリシー
with check
句を使用してinsertポリシーを指定できます。with check
式は、新しい行データがポリシー制約に適合することを保証します。
例えば、publicスキーマにprofiles
というテーブルがあり、ユーザーが自分自身のためにのみプロファイルを作成できるようにしたい場合、ユーザーIDが挿入しようとしている値と一致することを確認したいとします:
-- 1. テーブルを作成
create table profiles (
id uuid primary key,
user_id references auth.users,
avatar_url text
);
-- 2. RLSを有効にする
alter table profiles enable row level security;
-- 3. ポリシーを作成
create policy "Users can create a profile."
on profiles for insert
to authenticated -- 推奨されるPostgresロール
with check ( (select auth.uid()) = user_id ); -- 実際のポリシー
UPDATEポリシー
using
式とwith check
式の両方を組み合わせることでupdateポリシーを指定できます。
using
句は更新を許可するために真である必要がある条件を表し、with check
句は更新された行がポリシー制約に適合することを保証します。
例えば、publicスキーマにprofiles
というテーブルがあり、ユーザーが自分のプロファイルだけを更新できるようにしたい場合、using
句はユーザーが更新対象のプロファイルを所有しているかどうかをチェックし、with check
句は更新されたプロファイルが所有条件を満たしていることを確認します:
-- 1. テーブルを作成
create table profiles (
id uuid primary key,
user_id references auth.users,
avatar_url text
);
-- 2. RLSを有効にする
alter table profiles enable row level security;
-- 3. ポリシーを作成
create policy "Users can update their own profile."
on profiles for update
to authenticated -- 推奨されるPostgresロール
using ( (select auth.uid()) = user_id ) -- 既存の行がポリシー式に適合するかチェック
with check ( (select auth.uid()) = user_id ); -- 新しい行がポリシー式に適合するかチェック
with check
式が定義されていない場合、using
式が可視行の決定(通常のUSINGケース)と新しい行の追加を許可するかの決定(WITH CHECKケース)に使用されます。
DELETEポリシー
using
句を使用してdeleteポリシーを指定できます。
例えば、publicスキーマにprofiles
というテーブルがあり、ユーザーが自分のプロファイルだけを削除できるようにしたい場合:
-- 1. テーブルを作成
create table profiles (
id uuid primary key,
user_id references auth.users,
avatar_url text
);
-- 2. RLSを有効にする
alter table profiles enable row level security;
-- 3. ポリ
シーを作成
create policy "Users can delete a profile."
on profiles for delete
to authenticated -- 推奨されるPostgresロール
using ( (select auth.uid()) = user_id ); -- 実際のポリシー
ビュー
ビューはデフォルトでRLSをバイパスします。これは通常、postgres
ユーザーで作成されるためです。これはPostgresの機能であり、security definer
でビューが自動的に作成されます。
Postgres 15以降では、anon
およびauthenticated
ロールによって呼び出されたときに、基礎となるテーブルのRLSポリシーに従うビューを作成できます。security_invoker = true
を設定します。
create view <VIEW_NAME>
with(security_invoker = true)
as select <QUERY>
古いバージョンのPostgresでは、anon
およびauthenticated
ロールからのアクセスを取り消すか、公開されていないスキーマにビューを配置することでビューを保護します。
ヘルパー関数
Supabaseは、ポリシーの記述を容易にするためのヘルパー関数を提供しています。
auth.uid()
リクエストを行っているユーザーのIDを返します。
auth.jwt()
リクエストを行っているユーザーのJWTを返します。ユーザーのraw_app_meta_data
カラムまたはraw_user_meta_data
カラムに保存したものは、この関数を使用してアクセスできます。これらの違いを理解することが重要です:
raw_user_meta_data
- 認証済みユーザーがsupabase.auth.update()
関数を使用して更新できます。認可データを保存する場所としては適していません。raw_app_meta_data
- ユーザーによって更新されることはありません。したがって、認可データを保存する場所として適しています。
auth.jwt()
関数は非常に多用途です。例えば、app_metadata
にチームデータを保存している場合、特定のユーザーがチームに所属しているかどうかを判断するために使用できます。例えば、これがIDの配列であった場合:
create policy "User is in team"
on my_table
to authenticated
using ( team_id in (select auth.jwt() -> 'app_metadata' -> 'teams'));
JWTは常に「新鮮」であるわけではないことに注意してください。上記の例では、ユーザーをチームから削除してapp_metadata
フィールドを更新しても、ユーザーのJWTが更新されるまでauth.jwt()
を使用しても反映されません。
また、Authにクッキーを使用している場合は、JWTのサイズに注意してください。一部のブラウザは各クッキーに4096バイトの制限があるため、JWTの合計サイズがこの制限内に収まるようにする必要があります。
MFA
auth.jwt()
関数を使用して多要素認証 (MFA)をチェックできます。例えば、少なくとも2つのレベルの認証がある場合にのみユーザーがプロファイルを更新できるように制限することができます(Assurance Level 2):
create policy "Restrict updates."
on profiles
as restrictive
for update
to authenticated using (
(select auth.jwt()->>'aal') = 'aal2'
);
RLSのバイパス
SupabaseはRLSをバイパスするための特別な「サービス」キーを提供します。これらはブラウザで使用したり、顧客に公開したりすることは絶対に避けるべきですが、管理タスクには役立ちます。
Supabaseは、クライアントライブラリがサービスキーで初期化されていても、サインインしたユーザーのRLSポリシーに従います。
また、「bypass RLS」権限を持つ新しいPostgresロールを作成して、行レベルセキュリティをバイパスすることもできます:
grant bypassrls on "table_name" to "role_name";
これはシステムレベルのアクセスに役立ちます。この特権を持つPostgresロールのログイン資格情報を共有することは絶対に避けるべきです。