SupabaseのRow Level Securityについて
以下のドキュメントを翻訳しながら理解を深めます。
細かい認証ルールが必要な場合、PostgreSQLのRow Level Security (RLS - 行レベルセキュリティー)に勝るものはありません。
ポリシーはPostgreSQLのルールエンジンです。これは非常に強力で柔軟性があり、固有のビジネスニーズに合った複雑なSQLルールを書くことができます。
ポリシー
ポリシーは、コツをつかめば簡単に理解できます。各ポリシーはテーブルに関連付けられており、テーブルにアクセスするたびにポリシーが実行されます。ポリシーは、すべてのクエリにWHERE句を追加するようなものだと考えればよいでしょう。例えば、次のようなポリシーがあります。
create policy "Individuals can view their own todos."
on todos for select
using ( auth.uid() = user_id );
... ユーザーがTODOテーブルからselectしようとすると、このように変換されます。
select *
from todos
where auth.uid() = todos.user_id; -- ポリシーが暗黙のうちに付加されます。
ヘルパー機能
Supabaseでは、ポリシーで使用できる簡単な関数をいくつか用意しています。
auth.uid()
リクエストを行ったユーザーのIDを返します。
auth.role()
リクエストを行ったユーザーのロールを返します。ほとんどの場合、authenticated
またはanon
のいずれかになります。
auth.email()
リクエストを行ったユーザーのEメールを返します。
例
ここでは、PostgreSQLのRLSの能力を示すいくつかの例を紹介します。
読み取りアクセスを許可する
-- 1. テーブルを作成
create table profiles (
id uuid references auth.users,
avatar_url text
);
-- 2. RLSを有効化
alter table profiles
enable row level security;
-- 3. ポリシーを作成
create policy "Public profiles are viewable by everyone."
on profiles for select using (
true
);
- パブリックスキーマ(デフォルトスキーマ)にprofilesというテーブルを作成します。
- 行レベルのセキュリティを有効にします。
- すべてのセレクトクエリの実行を許可するポリシーを作成します。
アップデートの制限
-- 1. テーブルを作成
create table profiles (
id uuid references auth.users,
avatar_url text
);
-- 2. RLSを有効化
alter table profiles
enable row level security;
-- 3. ポリシーを作成
create policy "Users can update their own profiles."
on profiles for update using (
auth.uid() = id
);
- パブリックスキーマ(デフォルトスキーマ)にprofilesというテーブルを作成します。
- RLSを有効にします。
- ログインしているユーザーが自分のデータを更新できるようにするポリシーを作成します。
ジョインを含むポリシー
ポリシーにテーブルのジョインを含めることもできます。この例では、より高度なルールを構築するために、「外部」のテーブルを照会する方法を示しています。
create table teams (
id serial primary key,
name text
);
-- 2. 多対多のジョインを作成
create table members (
team_id bigint references teams,
user_id uuid references auth.users
);
-- 3. RLSを有効化
alter table teams
enable row level security;
-- 4. ポリシーを作成
create policy "Team members can update team details if they belong to the team."
on teams
for update using (
auth.uid() in (
select user_id from members
where team_id = id
)
);
SECURITY DEFINER関数を含んだポリシー
ポリシーでは、SECURITY DEFINER関数を使用することもできます。これは、多対多の関連で、リンクしたテーブルへのアクセスを制限したい場合に便利です。前述のチームとメンバーの例に続いて、この例では、SECURITY DEFINER定義関数をポリシーと組み合わせて使用し、メンバーテーブルへのアクセスを制御する方法を示します。
-- 1. 上記の「ジョインを含むポリシー」の例を参照
-- 2. RLSを有効化
alter table members
enable row level security
-- 3. SECURITY DEFINER関数を作成
create or replace function get_teams_for_authenticated_user()
returns setof bigint
language sql
security definer
set search_path = public
stable
as $$
select team_id
from members
where user_id = auth.uid()
$$;
-- 4. ポリシーを作成
create policy "Team members can update team members if they belong to the team."
on members
for all using (
team_id in (
select get_teams_for_authenticated_user()
)
);
メールアドレスのドメインを検証する
Postgresには、文字列の最右端n文字を返す関数right(string, n)
があります。これを使って、スタッフメンバーのメールアドレスのドメインを照合することができます。
-- 1. テーブルを作成
create table leaderboard (
id uuid references auth.users,
high_score bigint
);
-- 2. RLSを有効化
alter table leaderboard
enable row level security;
-- 3. ポリシーを作成
create policy "Only Blizzard staff can update leaderboard"
on leaderboard
for update using (
right(auth.email(), 13) = '@blizzard.com'
);
列のTTL(Time to live - 生存時間)
ポリシーは、InstagramのストーリーやSnapchatで見られるTTLや一定時間たつと消える機能の実装にも使用できます。以下の例では、Storiesテーブルの行は、過去24時間以内に作成されたものだけが利用可能となります。
-- 1. テーブルを作成
create table if not exists stories (
id uuid not null primary key DEFAULT uuid_generate_v4(),
created_at timestamp with time zone default timezone('utc' :: text, now()) not null,
content text not null
);
-- 2. RLSを有効化
alter table stories
enable row level security;
-- 3. ポリシーを作成
create policy "Stories are live for a day"
on stories
for select using (
created_at > (current_timestamp - interval '1 day')
);
``
高度なポリシー
SQLのパワーをフルに使って、極めて高度なルールを構築します。
この例では、postsテーブルとcommentsテーブルを作成し、別のポリシーに依存するポリシーを作成します。(この場合、コメントのポリシーはpostsのポリシーに依存します)。
create table posts (
id serial primary key,
creator_id uuid not null references auth.users(id),
title text not null,
body text not null,
publish_date date not null default now(),
audience uuid[] null -- many to many table omitted for brevity
);
create table comments (
id serial primary key,
post_id int not null references posts(id) on delete cascade,
user_id uuid not null references auth.users(id),
body text not null,
comment_date date not null default now()
);
create policy "Creator can see their own posts"
on posts
for select
using (
auth.uid() = posts.creator_id
);
create policy "Logged in users can see the posts if they belong to the post 'audience'."
on posts
for select
using (
auth.uid() = any (posts.audience)
);
create policy "Users can see all comments for posts they have access to."
on comments
for select
using (
exists (
select 1 from posts
where posts.id = comments.post_id
)
);
Tips
プライベートテーブルのリアルタイムを無効化
当社のリアルタイムサーバは、ユーザごとのセキュリティを提供していません。WebSocketのためのより強固な認証システムを実装できるまでは、プライベートテーブルのリアルタイム機能を無効にすることができます。これを行うには、Postgres レプリケーションのパブリケーション(公開)を管理します。
/**
* REALTIME SUBSCRIPTIONS
* 公開されたテーブルでのみ、リアルタイムでのリスニングを許可する。
*/
begin;
-- リアルタイムのパブリケーションを削除
drop publication if exists supabase_realtime;
-- パブリケーションを再作成しますが、どのテーブルに対しても有効にしません
create publication supabase_realtime;
commit;
-- パブリケーションにテーブルを追加
alter publication supabase_realtime add table products;
-- パブリケーションに他のテーブルを追加
alter publication supabase_realtime add table posts;
強化したリアルタイムのセキュリティを実装しているところです。
ポリシーを使用する必要はありません。
他の「バックエンド<->ミドルウェア<->フロントエンド」のアーキテクチャでセキュリティルールを作成するのと同じように、ミドルウェアに認証ルールを置くこともできます。
ポリシーはツールです。「サーバレスやJamstack」をセットアップする場合は、ミドルウェアを一切導入する必要がないので、特に効果的です。
しかし、アプリケーションに別の認証方法を使用したい場合は、それも問題ありません。Supabaseは「単なるPostgres」ですから、あなたのアプリケーションがPostgresで動作するならば、Supabaseでも動作します。
ヒント:すべてのテーブルでRLSを有効にして、テーブルにアクセスできないようにしてください。その上で、RLSをバイパスするように設計された、私たちが提供する「サービス」をご利用ください。
クライアントのサービスキーは絶対に使わないでください
Supabaseは、すべてのRLSをバイパスするために使用できる特別な「サービス」キーを提供しています。これらは、ブラウザで使用したり、クライアントに公開したりしてはいけませんが、管理作業には便利です。
記事としてまとめました。