Supabaseでサービスを公開するときのRLS設定について
はじめに
FirebaseやSupabaseなどのBaaS(Backend as a Service)を活用し、バックエンドを自前で構築せずにアプリを素早く開発する手法が広まっています。中でもSupabaseは、PostgreSQLをベースとした高機能なデータベースやAPI、認証機能などを簡単に扱える点が魅力です。
特に最近では、AIコーディングの進化により、Next.jsとSupabaseのような組み合わせで構築からリリースまでが非常に簡単に行えるようになっています。
しかし、Supabaseの便利さの裏には、セキュリティ上の落とし穴も存在します。特にRow-Level Security(RLS)を正しく設定していない場合、個人情報の漏洩やマスターデータの改ざんといった重大なリスクを引き起こす可能性があります。
本記事では、簡単な投稿サービスを例に、SupabaseのRLS設定について解説します。
Supabase と RLS について
Supabase の特徴とリスク
Supabaseは、クライアントから直接APIを介してデータベースを操作できる構成になっています。これにより開発効率は飛躍的に向上しますが、同時にRLS(行レベルセキュリティ)を適切に設定しないと、誰でも任意のテーブルにアクセス・操作が可能となるリスクがあります。
この状態では、データの漏洩、改ざん、不正な操作などの重大なインシデントにつながる可能性があります。
Row-Level Security(RLS)とは?
RLSは、PostgreSQLの機能で、どのユーザーがどの行にアクセスできるかを制御できる仕組みです。
Supabaseでは、RLSを有効化し、操作ごと(SELECT、INSERT、UPDATE、DELETE)に「ポリシー」を設定することで、安全にアクセス制御ができます。
RLSが無効になっていると、SELECT、INSERT、UPDATE、DELETEの全ての操作が可能な状態になるため、誰でも全てのデータにアクセス・操作できてしまい、全ユーザーの個人情報が閲覧可能になるなど非常に危険です。
投稿サービスを例にしたRLS設定の例
テーブル構成
-
auth_usersは、Supabase Authenticationを表します。 - ユーザーの公開情報と個人情報を分離し、マスターデータ(カテゴリ)は管理者のみが操作できるように設計しています。
-- 公開プロフィール(表示名や自己紹介など)
CREATE TABLE public_profiles (
id serial PRIMARY KEY,
auth_id uuid UNIQUE REFERENCES auth.users(id),
username text,
bio text,
created_at timestamptz DEFAULT now(),
updated_at timestamptz DEFAULT now()
);
-- 個人プロフィール(住所などの機微情報)
CREATE TABLE private_profiles (
id serial PRIMARY KEY,
auth_id uuid UNIQUE REFERENCES auth.users(id),
address text,
created_at timestamptz DEFAULT now(),
updated_at timestamptz DEFAULT now()
);
-- 投稿カテゴリ(マスターデータ、一般ユーザーは編集不可)
CREATE TABLE categories (
id serial PRIMARY KEY,
name text UNIQUE,
created_at timestamptz DEFAULT now(),
updated_at timestamptz DEFAULT now()
);
-- 記事投稿
CREATE TABLE articles (
id uuid PRIMARY KEY,
author_id uuid REFERENCES auth.users(id),
category_id integer REFERENCES categories(id),
title text,
content text,
is_public boolean DEFAULT false,
created_at timestamptz DEFAULT now(),
updated_at timestamptz DEFAULT now()
);
RLS 有効化
すべてのテーブルでRLSを明示的に有効にします。
ポリシーを定義していてもRLSを有効にしないと適用されません。
ALTER TABLE public_profiles ENABLE ROW LEVEL SECURITY;
ALTER TABLE private_profiles ENABLE ROW LEVEL SECURITY;
ALTER TABLE categories ENABLE ROW LEVEL SECURITY;
ALTER TABLE articles ENABLE ROW LEVEL SECURITY;
ポリシー定義
public_profiles(公開プロフィール)
公開プロフィールは誰でも参照できますが、作成・編集は本人のみ可能にします。
-- 誰でも公開プロフィールを参照可能
CREATE POLICY "Anyone can read public profiles"
ON public_profiles FOR SELECT
TO anon, authenticated
USING (true);
-- 自分の公開プロフィールのみ作成可能
CREATE POLICY "Users can insert own public profile"
ON public_profiles FOR INSERT
TO authenticated
WITH CHECK (auth_id = auth.uid());
-- 自分の公開プロフィールのみ編集可能
CREATE POLICY "Users can update own public profile"
ON public_profiles FOR UPDATE
TO authenticated
USING (auth_id = auth.uid());
private_profiles(個人情報)
個人情報は参照・作成・編集を本人のみ可能にします。
-- 自分の個人情報のみ参照可能
CREATE POLICY "Users can view their own private profile"
ON private_profiles FOR SELECT
TO authenticated
USING (auth_id = auth.uid());
-- 自分の個人情報のみ作成可能
CREATE POLICY "Users can insert own private profile"
ON private_profiles FOR INSERT
TO authenticated
WITH CHECK (auth_id = auth.uid());
-- 自分の個人情報のみ編集可能
CREATE POLICY "Users can update own private profile"
ON private_profiles FOR UPDATE
TO authenticated
USING (auth_id = auth.uid());
articles(記事)
公開記事は全員が閲覧可能とし、それ以外は自分の記事のみ操作可能にします。
-- 公開記事を全員が閲覧可能
CREATE POLICY "Public articles viewable"
ON articles FOR SELECT
TO anon, authenticated
USING (is_public = true);
-- 自分の投稿のみ操作可能
CREATE POLICY "Authors can view own articles"
ON articles FOR SELECT
TO authenticated
USING (author_id = auth.uid());
CREATE POLICY "Users can insert own articles"
ON articles FOR INSERT
TO authenticated
WITH CHECK (author_id = auth.uid());
CREATE POLICY "Users can update own articles"
ON articles FOR UPDATE
TO authenticated
USING (author_id = auth.uid());
CREATE POLICY "Users can delete own articles"
ON articles FOR DELETE
TO authenticated
USING (author_id = auth.uid());
categories(カテゴリ)
カテゴリは読み取りは誰でも可能とし、追加・変更などはSupabaseの管理者コンソールから行うものとします。
誰でも閲覧できるマスターデータですが、RLSを無効にすると誰でも改ざんが可能になるのでポリシーを設定します。
-- 読み取りは全員可能、書き込みは禁止(ポリシーを定義しない)
CREATE POLICY "Anyone can read categories"
ON categories FOR SELECT
TO anon, authenticated
USING (true);
その他の注意点
-
クライアントから参照しないテーブルや一時的に作成したテーブル、Edge Functionなどからのみ操作するテーブルであっても、必ずRLSを有効にしましょう。
-
複雑な認可条件が必要な場合、RLSのみで対応するのは推奨しません。Edge Functionなどのサーバーサイドで処理するか、Supabase以外のバックエンドサーバーの導入を検討してください。
さいごに
SupabaseのRLSは、柔軟かつ強力なアクセス制御を実現できますが、設定ミスひとつで重大な情報漏洩につながる可能性があります。
必ず全てのテーブルにRLSを適切に設定しましょう。
もし不安がある場合は、セキュリティの知識のあるエンジニアに相談するか、Supabaseのようにクライアントから直接DBへアクセスする方式をやめて、バックエンドサーバーを経由する構成にすることをおすすめします。
Discussion