🔑

Supabaseのセキュリティ対策をまとめてみた

2023/01/20に公開

はじめに

Supabase便利ですよね。直感的にデータベースを触ることができ、データ操作が容易にできる印象を持たれています。認証も簡単に導入できるのは強みですね。
しかし、使い方を間違えると誰もが簡単にデータ操作ができます。また、他人が自分のデータを書き換えることができてしまいます。

今回はSupabaseを使用する上で必要なセキュリティ対策についてまとめてみました。

セキュリティの重要性

JavaScriptを使用して認証、データ操作をするには anonkey(client key) が必要です。anonは匿名という意味合いがあります。

デフォルトでテーブルを作成した場合、anonkeyを使用して誰でもアクセスできてしまいます。
また、anonkey自体がクライアント側で使用する設計になっています。このままでは誰でもデータを取得、登録などできてしまいます。

そこでセキュリティ対策として2つほどやっておくべきことがあります。

テーブル権限変更

テーブルを作成するとデフォルトではanonkeyを使えば誰でもCRUD操作ができるようになっています。

既存権限を確認する

まずは既存のロール、テーブル、何が許可されているかを確認しましょう。

PostgreSQLで以下のSQLを入力します。
何も対策していなければgranteeにanonがあります。つまり、CRUD操作が全て可能になっていることになります。

SELECT grantee, table_name, privilege_type
FROM information_schema.role_table_grants
WHERE table_schema = 'public'
ORDER BY grantee, table_name, privilege_type;

anonの権限を剥奪していきます。これで認証ユーザー以外がテーブルにアクセスできなくなります。

REVOKE all privileges
ON all tables
IN SCHEMA public
FROM anon;

今後のデフォルト権限に備える

既存のテーブルに対しては権限を剥奪しましたが、新規テーブルを作成した場合はanon権限が付与されてしまいます。
それを見越して、テーブル作成時などにデフォルトで未認証ユーザーのアクセス権を無くすようにします。
Supabaseのデフォルト権限設定からanonの権限を全て取り除くようにします。テーブル、関数、シーケンスに関わる権限です。

ALTER DEFAULT privileges IN SCHEMA public REVOKE all ON tables FROM anon;
ALTER DEFAULT privileges IN SCHEMA public REVOKE all ON functions FROM anon;
ALTER DEFAULT privileges IN SCHEMA public REVOKE all ON sequences FROM anon;

anon権限を復元したくなった場合

削除した権限を復元したくなる場面があります。
その場合は以下のように権限を付与します。

これは、postsテーブルに対してSELECT文を使用する時にanon権限を付与する場合です。

GRANT SELECT ON posts TO anon;

Row Level Security(RLS)

PostgresSQLを使用した行単位のセキュリティになります。
この機能を使用することで、認証ユーザーのみデータを作成、更新ができるといった制御が可能になります。

ポリシーを作成する

作成するにあたり、理解しておくべき概念があります。
以下はポリシー作成する際、使用する項目になります。それらを順に説明していきます。

  • Policy name
    ポリシーの名前です。任意の名前をつけてください。
  • Allowed operation
    許可する操作です。SELECT、INSERT、UPDATE、DELETEがあります。
  • Target roles
    対象のロールです。誰に対して許可するかを設定します。
    データを全体に公開したい場合、publicを使用します。逆に認証ユーザーのみに公開したい場合はauthenticatedを使用します。
  • USING expression
    ここで指定した条件を満たす行を操作できるようになります。言い換えるならSQLでいうWHERE句になります。
  • WITH CHECK expression
    登録、更新をする際に指定した条件を満たす行のみ操作できるようになります。追加の条件を指定したい場合はこちらを使用します。
    満たされない場合はエラーが返されます。

データ取得

制限なくデータを取得するには以下のようなSQLを実行します。

CREATE policy "Allow public read access"
ON todos
FOR select
USING ( true );

ログインされたユーザーのデータのみ取得できるようにするには以下のようなSQLで実現可能です。

CREATE policy "View own todos." 
ON todos
FOR select
USING ( auth.uid() = user_id );

フロント側からリクエストする際に以下のように変換されてデータ取得できるようになります。認証ユーザーIDと登録されているユーザーIDが合致した行データを取得できるようになります。
このようにすることで、フロント側でクエリを書き換える必要がなくなります。

実際に実行されるSQL
SELECT *
FROM todos
WHERE auth.uid() = todos.user_id;

データ登録

登録ではWITH CHECKを使用します。
自身のデータのみ登録できるようにすることで、誤操作で他人のデータを登録することを防ぐことができます。

CREATE policy "Register own todo."
ON todos
FOR insert
TO authenticated
WITH CHECK ( ( auth.uid() = user_id ) );

データ更新

更新ではUSINGWITH CHECKを使用します。
更新する行データの絞り込みを行い、その後に自身のデータのみ更新できるようになります。

CREATE policy "Update own todo."
ON todos
FOR update
TO authenticated
USING ( ( auth.uid() = user_id ) )
WITH CHECK ( ( auth.uid() = user_id ) );

データ削除

削除ではUSINGを使用します。
以下では、認証ユーザーの行データの絞り込みを行い削除できるようになります。

CREATE policy "Delete own todo."
ON todos
FOR delete
TO authenticated
USING ( ( auth.uid() = user_id ) );

ヘルパー機能

RLSを作動させるためにはポリシーを作成する必要があります。
そのポリシーで使える便利な関数が存在します。

auth.uid()
auth.jwt()

いずれもログイン中のユーザーIDとJWTを取得できる関数になっています。

さいごに

Supabaseは簡単に導入できますが、セキュリティ対策をしなければ危ういということが伝わったでしょうか。
この記事がみなさんのSupabaseライフの一助になれば幸いです。

参考記事

この記事作成にあたり、下記の記事には大変お世話になりました。感謝しております。

https://zenn.dev/suin/scraps/23d4355df14a42
https://zenn.dev/hrtk/scraps/ed6e10fc462393

Discussion