この記事の要点(TL;DR)
- 投稿アプリで「公開の投稿はみんなに見せる、非公開の投稿は書いた本人だけに見せる」をやりたかった。
- Supabase(データベース+認証をまとめて使えるサービス)の RLS という仕組みを使うと、これがポリシー1行で書けました。
- キモは
using (is_public = true or auth.uid() = user_id)という条件です。
「RLSって聞くけどよく分からない」という初心者向けに、なるべく噛み砕いて書きます。
そもそもRLSって?
RLSは Row Level Security(行レベルセキュリティ) の略です。データベースの表(テーブル)に対して、「どの行を、誰が、見て/書いていいか」を行ごとに制限する仕組みです。
普通に考えると、この「誰に見せるか」の判定はアプリ側のプログラムで書きますよね。でもそれだと、うっかりチェックを書き忘れた画面から、他人の非公開データが漏れる危険があります。RLSはデータベース自身が門番になるので、アプリのどこからアクセスしても勝手にルールが適用されて安全、というのが大きなメリットです。
Supabaseはこの仕組みが最初から使えるようになっていて、しかも「今ログインしている人のID」を auth.uid() という書き方で条件の中に使えます。
やりたかったこと
「小さな善行を記録して積み立てる」個人開発アプリで、投稿にこういう仕様を付けました。
- 投稿には「公開」「非公開」を選べる(
is_publicという真偽値のカラム) - 公開の投稿はフィードで誰でも見られる
- 非公開の投稿は、書いた本人だけが自分のページで見られる(他人には存在も見せない)
これをアプリ側のif文でやると書き漏らしがこわいので、RLSでデータベースに守ってもらうことにしました。
テーブルの用意とRLSの有効化
まず投稿テーブルです。user_id に「誰の投稿か」、is_public に「公開かどうか」を持たせます。
create table kindness_entries (
id uuid primary key default gen_random_uuid(),
user_id uuid not null, -- 誰の投稿か
content text not null,
is_public boolean not null default true, -- 公開かどうか
created_at timestamptz not null default now()
);
-- ★ これを忘れると RLS が効かない(全部素通しになる)
alter table kindness_entries enable row level security;
最後の enable row level security が超重要です。これを実行して初めてRLSが働きます。有効化しただけの状態ではどの行も見えなくなる(許可ポリシーが1つも無いので全拒否)ので、次に「見ていい条件」を足していきます。
本題:閲覧を「公開 または 本人」に限定するポリシー
閲覧(select)のポリシーを1つ作ります。using (...) に書いた条件が true になる行だけが、その人に見える、という意味です。
create policy "read public or own" on kindness_entries
for select using (is_public = true or auth.uid() = user_id);
条件を日本語にすると「その行が公開(is_public = true) である、または その行のuser_idが、今ログインしている人のID(auth.uid())と一致する」です。
- 他人から見た場合:
is_public = trueの行だけ条件を満たす → 公開投稿だけ見える - 本人から見た場合:自分の行は
auth.uid() = user_idを満たす → 自分の非公開投稿も見える
たった1行で「公開はみんな・非公開は本人だけ」が実現できました。しかもこの条件をすり抜ける方法はアプリ側に用意されていない(データベースが門番なので)というのが安心ポイントです。
書き込みも本人だけに縛る
見るだけでなく「他人になりすまして投稿する」のも防ぎたいので、書き込み(insert)にもポリシーを付けます。insert は with check (...) で「これから入れようとしている行」を検査します。
create policy "insert own only" on kindness_entries
for insert with check (auth.uid() = user_id);
これで「保存しようとしている行の user_id が、自分のIDと一致していないと弾く」=他人のIDで投稿できないようになります。
つまずきやすいポイント
-
enable row level securityを実行し忘れる:ポリシーを書いても効きません。逆に有効化だけしてポリシーが無いと、全部見えなくなって「あれ?データが出ない」と焦ります(これは正常な挙動)。 -
usingとwith checkの使い分け:ざっくり「既にある行を見る/消す条件がusing」「これから入れる行を検査するのがwith check」です。select・deleteはusing、insertはwith check、updateは両方使えます。 -
auth.uid()はログインしている前提:未ログイン(匿名)だとauth.uid()は空になるので、匿名でも見せたい部分はusing (true)(誰でもOK)など別のポリシーにします。
まとめ
- RLSは「データベース自身がアクセスを制限する門番」。アプリのチェック漏れによる情報漏れを防げる。
- 「公開はみんな・非公開は本人だけ」は
using (is_public = true or auth.uid() = user_id)の1行でOK。 - テーブルごとに
enable row level securityを忘れない。 - 見る条件は
using、入れる条件はwith check。
個人開発でも「他人のデータが漏れる」のは一番やってはいけない事故なので、認証まわりを使うなら早めにRLSに慣れておくのがおすすめです。