Supabase を基本から一歩づつ理解する（Row Level Security 編）

# はじめに
新しいサービスを生み出していく際に、はじめから重厚長大な作りをすることは少なくなってきているかと思います。はじめはスピーディにサクッと作りたい、でも大きくなってきたらキッチリ作りたい。「サクッと」から「キッチリ」へ切り替える際に、データ構造がガラッと変わってしまうとサービスを育てていく時の課題になるのではないでしょうか。

![do-the-right-thing-1024x458.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127260/ac85a921-0688-47fe-86fb-2da2be272cc8.png)

（出典）[正しいものを正しくつくる](https://redjourney.jp/do-the-right/)


「サクッと」React をはじめとしたフロントエンドを開発する際に、バックエンドには `Firebase` や `Amplify` などを選択することが多いかと思います。私も両方とも好きでよく使っています。しかしながら基本的には `NoSQL` です。`RDB` で「サクッと」バックエンドを提供してくれるものが `Supabase` です。

https://supabase.com/

`Supabase` は「The Open Source Firebase Alternative / オープンソースの Firebase の代替」と自らを謳っており、慣れ親しんだリレーショナル・データベース（PostgreSQL）でありながら使いやすい BaaS（Backend as a Service）なのです。

本稿では、手を動かしながら `Supabase` を基本から一歩づつ理解していきたいと思います。前回は Database に触れました。今回は Row Level Security を深堀りしていきたいと思います。

# Supabase とは
読み方は「スーパーベース」と読みます。
主要な機能は以下です。

- [Database](https://qiita.com/t-kurasawa/items/4ee7feb2af0993a54f8c)
    - Row Level Security　←　イマココ
- Auth
- Storage
- Realtime
- Edge Functions

## Row Level Security (RLS) とは

https://supabase.com/docs/guides/database/postgres/row-level-security

Supabase はオープンソースの [PostgreSQL](https://www.postgresql.org/) を利用しています。30 年以上にわたって開発・改善されてきたオブジェクトリレーショナルデータベースシステムで、信頼性、機能の堅牢性、パフォーマンスの面で高い評価を得ています。

[Row Level Security (RLS) は PostgreSQL の機能](https://www.postgresql.org/docs/current/ddl-rowsecurity.html)です。RLSは、テーブル内の行へのアクセスを制御する機能です。RLS は SQL ルールを記述できます。Supabase の Auth 機能と組み合わせることでブラウザからデータベースまでの一貫した権限制御を行うことができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127260/7560382b-9c0e-47be-8c07-dd09d75589c0.png)

（出典）[Supabase README.jp.md](https://github.com/supabase/supabase/blob/master/i18n/README.jp.md)


### 注意点

:::note alert
Supabase では、RLS を有効にしている限り、ブラウザから便利かつ安全なデータ アクセスが可能になります。

公開されたスキーマに格納されているすべてのテーブルでは、RLS を常に有効にする必要があります。デフォルトでは `public` スキーマです。

ダッシュボードのテーブル エディターで作成されたテーブルでは、RLS がデフォルトで有効になっています。生の SQL または SQL エディターでテーブルを作成する場合は、RLS を自分で有効にすることを忘れないでください。

```SQL
alter table <schema_name>.<table_name>
enable row level security;
```

（出典）[Supabase Doc > RLS](https://supabase.com/docs/guides/database/postgres/row-level-security#row-level-security-in-supabase)
:::


### Policies
Postgres には `Policies` というルール機能があります。各ポリシーはテーブルに添付され、テーブルへアクセスするたびにポリシーが実行されます。

例）todos テーブルに SELECT 文を投げる時に、認証しているユーザーIDと todos テーブルのユーザーIDが一致しているかを確認するポリシー

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

todos テーブルへ SELECT 文を投げる時には WHERE 句が追加された状態になる。

```SQL
select *
from todos
where auth.uid() = todos.user_id;
-- Policy is implicitly added.
```

（出典）[Supabase Doc > Policies](https://supabase.com/docs/guides/database/postgres/row-level-security#policies)



### Supabase で RLS を有効にする
Supabase はテーブルを作成すると自動的に API 経由で CRUD できるようになります。RLS を有効にすると、ポリシーを作成するまでは公開キー（anon key）を使用してAPI経由でデータにアクセスできなくなります。Supabase の GUI で設定も出来ますが、環境保持の観点から SQL を作成して Git 管理をおすすめします。


```SQL
alter table "table_name" enable row level security;
```

books テーブルで RLS が未設定のため GUI で有効にしていきます。
（※デフォルトではRLSは有効な状態になってますが説明のため）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127260/5d882547-037f-4252-8c7d-74617ec5236f.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127260/b2dd0e09-f1d5-4fa2-b549-2433e5e8e516.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127260/6db5e939-0148-4d3e-b954-136787a323ed.png)

有効になりましたのでポリシーを設定します。
Add RLS policy > Create policy 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127260/c959f4e1-9f20-4869-9cc9-529a85491fb8.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127260/99dc982b-fbd9-48e2-9918-849c94f55794.png)

SELECT 文のポリシーで `Enable read access for all users` を books テーブルに設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127260/a99f280c-9fd7-4eb0-8f69-06fb72bf66cf.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127260/a2fb0950-63ea-4aff-92e6-fee851ca3e10.png)


```SQL
alter policy "Enable read access for all users"

on "public"."books"
to public
using (
  true
);
```



### 認証済ロールと未認証ロール
Supabase はすべてのリクエストを認証済／未認証という Postgres のロールに分類します。

- `authenticated` : 認証済リクエスト（ユーザーがログインしている）
- `anon` : 未認証リクエスト（ユーザーがログインしていない）

:::note warn
匿名ユーザ（anonymous user）とanonキーの違い

`anon` という Postgres ロールは、Supabase Auth の `匿名ユーザ` とは異なります。匿名ユーザは「authenticated 認証されたロール」としてデータベースへアクセス可能です。
JWT の claim で is_anonymous をチェックすることで `永続ユーザ` と区別できます。
:::

上記は公式の解説になりますが、少し補足します。


**1. anon ロール**
Supabase では、PostgreSQL のロール（ユーザーのようなもの）として anon ロール を用意しています。このロールは、認証されていない（未ログインの）ユーザーがデータベースへアクセスするときに使用します。

anon ロールは Supabase の API キー (anon key) を使ったアクセス に関連しています。例えば、anon キーを使って直接 Supabase の REST API を叩くと、そのリクエストは anon ロールとして処理されます。


**2. Supabase Auth の「匿名ユーザー」**
Supabase Auth では「匿名ユーザー（anonymous user）」という概念があります。ログインはしているが、匿名として扱うユーザーを指します。

Supabase Auth の「匿名ユーザー」は 認証済み（authenticated） の状態になり、データベースでは authenticated ロールとして動作します。
しかし、通常のユーザーとは異なり、JWT の is_anonymous クレーム（JWT の中に含まれる情報）を見れば、匿名ユーザーかどうかを判別できます。


| 項目               | `anon` ロール              | 匿名ユーザー（anonymous user） |
|--------------------|--------------------------|-----------------------------|
| 認証の有無        | 認証なし                  | 認証あり                    |
| 使われるロール    | `anon`                    | `authenticated`             |
| どこで使われる？  | `anon` キーでのアクセス  | Supabase Auth で匿名ログイン |
| JWT で識別可能？  | ×（そもそも JWT なし）   | `is_anonymous` クレームをチェック |


例１）認証なしの API アクセスは `anon` ロールとなる

```JavaScript
const { data } = await supabase
  .from('public_table')
  .select('*');
```

例２）Supabase Auth の匿名ユーザーは、JWT の is_anonymous クレームが true になる。

```JavaScript
const { user } = await supabase.auth.signInAnonymously();
console.log(user);
```

要するに、Supabase では 「認証されていないアクセス（anon ロール）」 と 「認証はされたが匿名なユーザー（Supabase Auth の anonymous）」 は異なるものとして扱うということです。


（出典）[Supabase Doc > Authenticated and unauthenticated roles](https://supabase.com/docs/guides/database/postgres/row-level-security#enabling-row-level-security)



### Policies の作成方法
SELECT, INSERT, UPDATE, DELETE, View に対応するポリシーの作成方法があります。公式ドキュメントや Supabase ダッシュボード画面にサンプルとなるポリシーのSQLがありますので、そちらをご参照ください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127260/042f1543-0565-443b-9122-a5c92ca60422.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127260/7afcb555-4f39-4542-8b07-c76994979c2f.png)

（出典）[Supabase Doc > Creating policies](https://supabase.com/docs/guides/database/postgres/row-level-security#creating-policies)


### ヘルパー関数
ポリシーを作成するためのヘルパー関数が準備されています。
これまでも目に入ってたかと思いますが `auth.uid()` や `auth.jwt()` などです。詳しくは公式ドキュメントを参照ください。

（出典）[Supabase Doc > Helper functions](https://supabase.com/docs/guides/database/postgres/row-level-security#helper-functions)

### RLS の制約なしにデータへアクセスする方法（管理専用）
RLS は公開サイトやブラウザ・ユーザーからのアクセスを想定した権限制御です。サーバー側で閉じたネットワーク内での管理用途では RLS をバイパス（無視）してデータにアクセスしたいユースケースでは `Service Key` を使います。

#### Service Key でのバイパス
Supabase には `Service Key` という特別な API キーがあります。このキーを使うと RLS を無視して データにアクセスできます。ただし、これは 管理用（バックエンド）専用 であり、ブラウザやユーザーには絶対に公開してはいけません。

`Service Key` を使っても、サインインしたユーザーの RLS は適用されます。つまり、RLS を完全にバイパスしたいなら、ユーザーの認証を使わないシステム的な処理 にする必要があります。

※PostgreSQL の `bypassrls` 権限を使えば、RLS を完全に無視できますが漏洩した場合にセキュリティリスクがありますので推奨はしません。


（出典）[Supabase Doc > Bypassing Row Level Security](https://supabase.com/docs/guides/database/postgres/row-level-security#bypassing-row-level-security)

### 性能面での推奨
RLS は権限制御において便利ですが、性能に影響します。
インデックスの他、Supabase ならではの Tips が公式ドキュメントに記載されていますのでご参照ください。


（出典）[Supabase Doc > RLS performance recommendations](https://supabase.com/docs/guides/database/postgres/row-level-security#rls-performance-recommendations)

### 参考
解説しきれませんでしたが、以下もご参考ください。

- [データベーステスト](https://supabase.com/docs/guides/database/testing)
- [RLS性能対策のベストプラクティス](https://github.com/orgs/supabase/discussions/14576)

# おわりに
以上で Row Level Security (RLS) 編を終わります。
次回は Auth 編・・・と言いたいところですが Column Level Security（列レベルセキュリティ）を深堀りしていきます。

https://supabase.com/docs/guides/database/postgres/column-level-security