SupabaseのRow Level Security (RLS) を理解する
Supabaseのデータベースのセキュリティを担う「**Row Level Security**(行レベルセキュリティ)」とは、その名の通り、データベースの各行に対して個別にセキュリティ設定を行う機能です。
Supabaseのデータベースのセキュリティを担う「Row Level Security(行レベルセキュリティ)」とは、その名の通り、データベースの各行に対して個別にセキュリティ設定を行う機能です。
これにより、特定の行に対するアクセスを制限し、より細かいセキュリティ管理が可能となります。
この機能を実現するために、Policy(ポリシー)と呼ばれるセキュリティルールを定義したオブジェクトを使用します。ポリシーを定義し、テーブルに適用することで、どのユーザーがどの操作を実行できるかを行単位で管理できます。
Firebaseに馴染みのある方は、Firestoreの「セキュリティルール」と同様の概念と考えていただければわかりやすいでしょう。
元々はPostgreSQLの機能ですが、Supabaseではコンソールからボタンをクリックするだけで簡単に設定できます。
Row Level Security | Supabase Docs
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
句を追加するようなものです。例えば、以下のようなポリシーは...
... ユーザーがtodosテーブルから選択しようとするときに以下のように変換されます:
行レベルセキュリティの有効化
任意のテーブルに対してenable row level security
句を使用してRLSを有効にすることができます:
RLSを有効にすると、ポリシーを作成するまでpublicのanon
キーを使用してAPI経由でデータにアクセスできなくなります。
認証済みおよび未認証のロール
Supabaseは各リクエストを以下のロールのいずれかにマッピングします:
anon
: 未認証のリクエスト(ユーザーがログインしていない)authenticated
: 認証済みのリクエスト(ユーザーがログインしている)
これらは実際にはPostgresのロールです。これらのロールは、TO
句を使用してポリシー内で使用できます:
匿名ユーザーとanonキーの違い:
anon
Postgresロールを使用することは、Supabase Authにおける匿名ユーザーとは異なります。匿名ユーザーはデータベースにアクセスするためにauthenticated
ロールを引き受け、JWTのis_anonymous
クレームをチェックすることで永続的なユーザーと区別することができます。
ポリシーの作成
ポリシーは単にPostgresテーブルに付属するSQLロジックです。各テーブルにいくつでもポリシーを付けることができます。
Supabaseは、Supabase Authを使用している場合にRLSを簡単にするためのいくつかのヘルパーを提供しています。これらのヘルパーを使用して基本的なポリシーを説明します:
SELECTポリシー
using
句を使用してselectポリシーを指定できます。
例えば、publicスキーマにprofiles
というテーブルがあり、誰でも読み取れるようにしたいとします。
あるいは、ユーザーが自分のプロファイルだけを見られるようにしたい場合:
INSERTポリシー
with check
句を使用してinsertポリシーを指定できます。with check
式は、新しい行データがポリシー制約に適合することを保証します。
例えば、publicスキーマにprofiles
というテーブルがあり、ユーザーが自分自身のためにのみプロファイルを作成できるようにしたい場合、ユーザーIDが挿入しようとしている値と一致することを確認したいとします:
UPDATEポリシー
using
式とwith check
式の両方を組み合わせることでupdateポリシーを指定できます。
using
句は更新を許可するために真である必要がある条件を表し、with check
句は更新された行がポリシー制約に適合することを保証します。
例えば、publicスキーマにprofiles
というテーブルがあり、ユーザーが自分のプロファイルだけを更新できるようにしたい場合、using
句はユーザーが更新対象のプロファイルを所有しているかどうかをチェックし、with check
句は更新されたプロファイルが所有条件を満たしていることを確認します:
with check
式が定義されていない場合、using
式が可視行の決定(通常のUSINGケース)と新しい行の追加を許可するかの決定(WITH CHECKケース)に使用されます。
DELETEポリシー
using
句を使用してdeleteポリシーを指定できます。
例えば、publicスキーマにprofiles
というテーブルがあり、ユーザーが自分のプロファイルだけを削除できるようにしたい場合:
ビュー
ビューはデフォルトでRLSをバイパスします。これは通常、postgres
ユーザーで作成されるためです。これはPostgresの機能であり、security definer
でビューが自動的に作成されます。
Postgres 15以降では、anon
およびauthenticated
ロールによって呼び出されたときに、基礎となるテーブルのRLSポリシーに従うビューを作成できます。security_invoker = true
を設定します。
古いバージョンの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の配列であった場合:
JWTは常に「新鮮」であるわけではないことに注意してください。上記の例では、ユーザーをチームから削除してapp_metadata
フィールドを更新しても、ユーザーのJWTが更新されるまでauth.jwt()
を使用しても反映されません。
また、Authにクッキーを使用している場合は、JWTのサイズに注意してください。一部のブラウザは各クッキーに4096バイトの制限があるため、JWTの合計サイズがこの制限内に収まるようにする必要があります。
MFA
auth.jwt()
関数を使用して多要素認証 (MFA)をチェックできます。例えば、少なくとも2つのレベルの認証がある場合にのみユーザーがプロファイルを更新できるように制限することができます(Assurance Level 2):
RLSのバイパス
SupabaseはRLSをバイパスするための特別な「サービス」キーを提供します。これらはブラウザで使用したり、顧客に公開したりすることは絶対に避けるべきですが、管理タスクには役立ちます。
Supabaseは、クライアントライブラリがサービスキーで初期化されていても、サインインしたユーザーのRLSポリシーに従います。
また、「bypass RLS」権限を持つ新しいPostgresロールを作成して、行レベルセキュリティをバイパスすることもできます:
これはシステムレベルのアクセスに役立ちます。この特権を持つPostgresロールのログイン資格情報を共有することは絶対に避けるべきです。