0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Supabaseで「公開の投稿は誰でも、非公開は本人だけ」を1行で実現する(RLS入門)

0
Posted at

この記事の要点(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)にもポリシーを付けます。insertwith 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 を実行し忘れる:ポリシーを書いても効きません。逆に有効化だけしてポリシーが無いと、全部見えなくなって「あれ?データが出ない」と焦ります(これは正常な挙動)。
  • usingwith 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に慣れておくのがおすすめです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?