0
1

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で個人開発するときの「動いてるけど危ない設計」7選

0
Posted at

はじめに

Supabaseで個人開発をしていると、想像以上に早くアプリの形になります。
認証もある。DBもある。Storageもある。フロントからそのまま触れて、数日で「それっぽいもの」ができる。このスピード感は本当に魅力です。

ただ、その速さゆえに、あとから振り返ると「動いてはいたけど、あれは普通に危なかったな」と思う設計が結構あります。
特に個人開発では、まず作ってみることが正義になりやすいので、セキュリティや責務分離が後回しになりがちです。
Supabaseの公式ドキュメントでも、anon や publishable key で使う場合は、RLSを有効にし、権限を継続的に見直すことが前提だとされています。

今回は、私自身もやりがちだと感じる「動いてるけど危ない設計」を7つに絞って整理します。
早い段階で意識しておくと、あとで効いてきます。

1. service_role をアプリ側で雑に使う

これは一番危ないです。しかも、最初は便利すぎて危険性に気づきにくいです。

たとえば「RLSで詰まったから、とりあえず service_role を使えば全部動く」という発想です。
確かに動きます。でもそれは、設計が正しいから動いているのではなく、強い鍵で全部開けているだけです。

Supabase公式でも、service_role と secret key は開発者が管理する安全なサーバー側でのみ使うべきで、service_role は BYPASSRLS 属性を持ち、RLSをすべて迂回すると明記されています。ブラウザで使うことも禁止されています。

個人開発でやりがちなのは、フロント側の .env に入れてしまうことです。ローカルでは動きますし、実装も楽です。
でも、それは「認可設計を考えなくて済んでいる」のではなく、「認可を飛ばしている」状態です。

service_role は、管理画面、バッチ、集計処理、サーバーサイドの厳格な内部処理など、明確に限定された場所でだけ使うべきです。
通常のアプリ利用者が触る画面やクライアント処理では、anon / publishable key と RLS で成立する設計に寄せるべきです。

2. 「ログインしてるから安全」と思っている

これはRLSの話と地続きですが、かなり多い勘違いです。

Supabase Authでログイン機能をつけると、急にアプリがちゃんとして見えるようになります。
ですが、認証と認可は別物です。

ログインできることと、そのユーザーがどのデータを読めて、どの行を更新できるかは、別で設計しないといけません。
SupabaseのAuthドキュメントでも、authentication は本人確認、authorization はその人に何が許可されるか、という別の責務として整理されています。

よくあるのは、「ログイン済みなら全件取得OK」になっている状態です。
たとえば posts テーブルに対して、authenticated なら select を許す、みたいなポリシーです。これだと、誰でも全ユーザーのデータを見られます。

個人開発の初期は「まだユーザー少ないし」で流しがちですが、その設計のまま機能が増えると、途中から直すのがかなりしんどくなります。
大事なのは、「ログインしているか」ではなく「その行にアクセスしていい本人か」で考えることです。

3. profiles を雑に作る、あるいは auth.users の扱いを曖昧にする

Supabaseを触り始めると、ユーザー情報をどこに持つかで一度は迷います。

ここで危ないのは、auth.users をそのままアプリのユーザー情報テーブルのように扱おうとすることです。
Supabase公式では、Auth schema は自動生成APIに公開されておらず、API経由で扱いたいユーザー情報は public スキーマに自前テーブルを作る形が案内されています。さらに public.profiles のようなテーブルを作り、auth.users を参照しつつ on delete cascade を指定する例も示されています。

この設計を曖昧にすると、あとでだいたい壊れます。
たとえば、profiles.id と auth.users.id が一致しない、削除時に関連データが残る、users 的な独自テーブルを二重管理してしまう、などです。

個人開発では、「名前・アイコン・自己紹介」みたいなアプリ用プロフィール情報は public.profiles に寄せ、認証基盤としてのユーザー管理は auth.users に寄せる、という責務分離を最初から意識したほうが後で楽です。
雑に始めても動きますが、データ整合性の観点ではかなり危なめです。

4. insert 時の user_id をクライアント任せにする

これも動いてしまうので怖いポイントです。

たとえば、フロント側で user_id: session.user.id を付けて insert する実装はよくあります。もちろん、それ自体は普通に書けますし、開発中は問題なく見えます。ただ、クライアントが渡す値をそのまま信じる設計は、境界の置き方として弱いです。

本来は、「この行の所有者は誰か」をDB側でも一貫して検証できる形に寄せるべきです。RLSで auth.uid() = user_id を見るなら、少なくとも insert ポリシーまで含めて整合するようにしておかないと、「見える条件」と「作れる条件」がズレます。
RLSが有効なのにデータが返らないケースは、ポリシー未設定や、認証付きリクエストになっていないことがよくあると、Supabaseのトラブルシュートでも説明されています。

個人的には、クライアントで user_id を渡すにしても、「DB側のポリシーで必ず本人しか作れない」「更新も本人だけ」「削除も本人だけ」までセットで考えるべきだと思っています。
値を渡しているだけで安全が担保されるわけではありません。

5. Storage をファイル置き場としてだけ見ている

Storageも危ないです。画像アップロードが動いた瞬間に安心しやすいのですが、ここもちゃんと認可設計が必要です。

Supabase Storage は Postgres のRLSと連携していて、アップロード可否などは storage.objects に対するポリシーで制御します。
しかも、デフォルトではRLSポリシーなしに自由アップロードできるわけではなく、必要な操作を明示的に許可する設計です。

つまり、DBだけRLSを頑張って、Storageはバケット作って終わり、では不十分です。
プロフィール画像、投稿画像、添付ファイルなど、どのパスに、誰が、何を、どこまで操作できるのかを考えないと、意図しない参照や上書きの温床になります。

個人開発だと「画像だからまあいいか」となりがちですが、ファイルはむしろ漏れると分かりやすく事故になります。DBの行単位だけでなく、Storageのオブジェクト単位でも権限設計が必要です。

6. RLSをセキュリティ機能としてしか見ておらず、性能を無視する

RLSはセキュリティの話ですが、実運用では性能とも直結します。

Supabaseのベストプラクティスでも、RLSで使う列にインデックスを張ること、auth.uid() や auth.jwt() を毎行評価するような書き方を避ける工夫が示されています。
特に、RLS条件に使う user_id のような列にインデックスがないと、大きなテーブルで急に重くなります。公式のトラブルシュートでも、該当列へのインデックスで大きな改善が見られるケースが紹介されています。

個人開発の初期はデータ件数が少ないので見えません。
でも、投稿、いいね、コメント、学習記録、ワークアウトログのように件数が積み上がるサービスでは、後からじわじわ効いてきます。

危ないのは、「安全だからOK」で終わることです。正しくは、「安全で、かつ現実的な速度で動くか」を見ないといけません。RLSは書いて終わりではなく、テーブル設計やインデックス設計とセットです。

7. RLSを絞り込みの代わりにしている

これも地味ですが、かなり大事です。

たとえば「RLSで auth.uid() = user_id にしてるから、クエリでは全件 select() でいいでしょ」と考えるパターンです。
もちろんセキュリティ上はそれで守れるかもしれません。
ただ、SupabaseのRLSベストプラクティスでは、RLSはあくまでセキュリティ境界として使い、必要なフィルタはクエリ側でも書くことが推奨されています。
.eq('user_id', userId) のような条件を明示したほうが、意図もはっきりしますし、性能上も有利です。

これは思想の話でもあります。
RLSは最後の防波堤であって、普段のアプリの問い合わせロジックそのものではありません。
アプリ側はアプリ側で、必要なデータだけを取りに行く。
DB側は、それでも越えてはいけない境界を守る。この二段構えのほうが健全です。

「RLSを書いたからクエリは雑でいい」という状態は、短期的には楽ですが、運用が始まると読みづらさも重さも増えていきます。

おわりに

Supabaseは本当に便利です。個人開発との相性もかなりいいです。
だからこそ、危ない設計でも一度“動いてしまう”のが難しいところだと思います。

特に個人開発では、最初はスピード優先でいい場面もあります。
ただ、そのスピードを活かすためにも、「何を雑にしてよくて、何は最初から外してはいけないか」は知っておいたほうがいいです。service_role をフロントで使わないこと、認証と認可を分けて考えること、profiles と auth.users の責務を分けること、StorageにもRLSが必要だと理解すること。このあたりは、後回しにすると修正コストが一気に跳ね上がります。

前回のRLSの記事が刺さったのは、たぶん「個人開発で見落としやすいけど、本当は大事」というポイントに触れていたからだと思います。今回の話も同じです。動いていることと、安全で拡張しやすいことは、まったく別です。

Supabaseは“楽に作れる”からこそ、“雑に作れてしまう”。そこを越えたあたりから、個人開発の質が一段上がる気がしています。

0
1
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
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?