🐡

Supabaseの行単位セキュリティーについて学ぶ

2021/12/14に公開

前回、Next.jsとSupabaseで認証の仕組みを実装しました。

https://zenn.dev/hrtk/articles/3da84e46c97267

その際、データベースの更新に関して、データへアクセスするための仕組みについて詳しくは触れていませんでした。

Supabaseでは、認証したユーザーがデータにアクセスするための仕組みとして、行単位セキュリティーという仕組みを提供しています。

今回は、行単位セキュリティーについて学んだことをまとめていきます。

以下のサイトに掲載されている記事とビデオの内容をベースにしています。

https://www.supabase.jp/docs/guides/auth/row-level-security

背景

いままで、クライアントからミドルウェアとしてAPIを介してPostgreSQLなどのデータソースに接続するアーキテクチャーを採用されていました。

APIをNodejsやRailsなどで構築して、データへの権限管理はミドルウェアで実装していました。

従来の権限管理

最近では、フロントでの処理の比重が高まってきたこともあり、よりシンプルなアーキテクチャーが求められています。

Supabaseでは、supbase-jsライブラリーとPostgreSQLに行単位セキュリティーの仕組みがはいっています。それにより、クライアントからミドルウェアを介すことなくシームレスに権限管理されたデータへアクセスできるようになります。

supabaseの権限管理

ポリシー

テーブルに対して行単位セキュリティーを有効にすると、そのテーブルのデータにアクセスできなくなります(匿名キー - anon keyによるアクセスにおいて)。ポリシーを作成することにより、それぞれの行に対してどのようなルールでアクセスできるのかを制御します。

ポリシーは、テーブルへアクセスするたびに実行されます。その際に、ポリシーに記述されたアクセス・ルールが変換されて、SQLが実行されます。

例えば、下記のポリシーを作成したとします。

create policy "Individuals can view their own todos." 
  on todos for select 
  using ( auth.uid() = user_id );

ユーザーがtodosテーブルにselectを発行するたびに、次のように変換されます。

select *
  from todos
  where auth.uid() = todos.user_id; -- ポリシーが暗黙の内に追加されます。

ヘルパー関数

先ほどの例のauth.uid()のように、Supabaseには、ポリシーで使用できる簡単な関数が用意されています。

auth.uid()

リクエストを行ったユーザーのIDを返します。

auth.role()

リクエストを行ったユーザーのロールを返します。ほとんど、authenticated(認証済み)またはanon(匿名)のいずれかが返ります。

auth.email()

リクエストを行ったユーザーのメール・アドレスを返します。

実際にどのような制限ができるのか、具体的なSQLをみていきましょう。

読み取りアクセスの許可

公開されているプロフィールはどのユーザーでも見られるようにします。

-- 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. すべてのselectクエリの実行を許可するポリシーを作成します。

更新の制限

プロフィールの内容は自分しか更新できないようにします。

-- 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. ログインしたユーザーが自分のデータを更新することを許可するポリシーを作成します。

結合を含むポリシー

ポリシーには、テーブルの結合を含めることもできます。この例では、より高度なルールを構築するために、「外部」のテーブルにクエリを実行する方法を示しています。

チームに所属しているユーザーだけがチームの情報を更新できるようにします。

-- 1. テーブルを作成
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
    )
  );

TTL

Instagramのストリーズなどで、ある一定の期間だけ見られるように、TTL(Time To Live)機能も実装できます。

次の例では、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')
  );

おわりに

Postgresはよく使っていましたが、Supabaseを使いはじめてから行単位セキュリティーとポリシーを使うようになりました。

このあたりは、ミドルウェアで実装していたので、SQLでここまでできるのは正直驚きです。

また、アーキテクチャーをシンプルにできるため、メンテナンス・コストを削減できるのが期待できます。

参考

Supbaseの知識を深めるために、ドキュメントの翻訳に取り組んでいます。

今回紹介した内容の仕組みについて、こちらのシリーズが参考になりました。理解のお役立てください。

https://www.supabase.jp/docs/learn/auth-deep-dive/auth-deep-dive-jwts

GitHubで編集を提案

Discussion