Closed15

SupabaseのRow Level Securityについて

hirotakahirotaka

細かい認証ルールが必要な場合、PostgreSQLのRow Level Security (RLS - 行レベルセキュリティー)に勝るものはありません。

ポリシーはPostgreSQLのルールエンジンです。これは非常に強力で柔軟性があり、固有のビジネスニーズに合った複雑なSQLルールを書くことができます。

hirotakahirotaka

ポリシー

ポリシーは、コツをつかめば簡単に理解できます。各ポリシーはテーブルに関連付けられており、テーブルにアクセスするたびにポリシーが実行されます。ポリシーは、すべてのクエリに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; -- ポリシーが暗黙のうちに付加されます。
hirotakahirotaka

ヘルパー機能

Supabaseでは、ポリシーで使用できる簡単な関数をいくつか用意しています。

auth.uid()
リクエストを行ったユーザーのIDを返します。

auth.role()
リクエストを行ったユーザーのロールを返します。ほとんどの場合、authenticatedまたはanonのいずれかになります。

auth.email()
リクエストを行ったユーザーのEメールを返します。

hirotakahirotaka

ここでは、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
  );
  1. パブリックスキーマ(デフォルトスキーマ)にprofilesというテーブルを作成します。
  2. 行レベルのセキュリティを有効にします。
  3. すべてのセレクトクエリの実行を許可するポリシーを作成します。
hirotakahirotaka

アップデートの制限

-- 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
  );
  1. パブリックスキーマ(デフォルトスキーマ)にprofilesというテーブルを作成します。
  2. RLSを有効にします。
  3. ログインしているユーザーが自分のデータを更新できるようにするポリシーを作成します。
hirotakahirotaka

ジョインを含むポリシー

ポリシーにテーブルのジョインを含めることもできます。この例では、より高度なルールを構築するために、「外部」のテーブルを照会する方法を示しています。

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
    )
  );
hirotakahirotaka

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()
    )
  );
hirotakahirotaka

メールアドレスのドメインを検証する

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'
  );
hirotakahirotaka

列の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')
  );
``
hirotakahirotaka

高度なポリシー

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
  )
);
hirotakahirotaka

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;

強化したリアルタイムのセキュリティを実装しているところです。

hirotakahirotaka

ポリシーを使用する必要はありません。

他の「バックエンド<->ミドルウェア<->フロントエンド」のアーキテクチャでセキュリティルールを作成するのと同じように、ミドルウェアに認証ルールを置くこともできます。

ポリシーはツールです。「サーバレスやJamstack」をセットアップする場合は、ミドルウェアを一切導入する必要がないので、特に効果的です。

しかし、アプリケーションに別の認証方法を使用したい場合は、それも問題ありません。Supabaseは「単なるPostgres」ですから、あなたのアプリケーションがPostgresで動作するならば、Supabaseでも動作します。

ヒント:すべてのテーブルでRLSを有効にして、テーブルにアクセスできないようにしてください。その上で、RLSをバイパスするように設計された、私たちが提供する「サービス」をご利用ください。

hirotakahirotaka

クライアントのサービスキーは絶対に使わないでください

Supabaseは、すべてのRLSをバイパスするために使用できる特別な「サービス」キーを提供しています。これらは、ブラウザで使用したり、クライアントに公開したりしてはいけませんが、管理作業には便利です。

このスクラップは2021/12/14にクローズされました