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で学ぶ「認証・認可」の仕組みと注意点

0
Posted at

はじめに

個人開発で Next.js + Supabase を使っていると、その開発速度の速さに感動する一方で、ある巨大な不安にぶつかります。

「セキュリティ、本当に一人で担保できてる…?」

Webサービスを作る上で避けて通れないのが「認証(Authentication)」と「認可(Authorization)」です。

特にSupabaseのようなBaaS(Backend as a Service)では、認証周りの処理を簡単に実装できる反面、どこまでがSupabase任せで、どこからが開発者自身の責任なのか の境界線があいまいになりがちです。
一歩設定を間違えると、誰でも他人のデータを閲覧・操作できてしまうリスクも潜んでいます。

本記事では、Supabaseを題材に 認証・認可の根本的な違いと仕組み を整理しつつ、
開発者が 実務や個人開発で絶対に気をつけるべきセキュリティの注意点 について調べた内容をまとめました。

セキュリティに対する苦手意識をなくし、安全にプロダクトを公開するための参考になれば幸いです。

この記事で得られる観点

  • 認証認可 の違い
  • Supabase が 自動で担う部分 と、開発者が 自分で設計すべき部分 の境界
  • RLS(Row Level Security) の基本と、USING / WITH CHECK の使い分け
  • API キーログイン情報(JWT) について
  • 個人開発でも避けたい アンチパターン と、確認すべきチェック項目

認証・認可ってそもそも何?

私が混乱しやすかったのは、どちらも「ログイン周り」と一括りに考えてしまう点です。
実際には、「誰か」を確かめる工程 と、「何をしてよいか」を決める工程 は別物です。

身近なたとえで言うと、マンションの入館 に似ています。

概念 たとえ 役割
認証(Authentication) エントランスで「本人確認」 この人は登録済みの住人か
認可(Authorization) 各部屋の鍵の有無 この人は この部屋 に入ってよいか

認証に成功しても、データベース側で誰に何を許すかを決めていなければ、「ログインはできたが、他人のデータも見える」という状態になり得ます。
Supabase ではログイン UI を用意するだけでは不十分で、データベース側の認可設計 が別途必要です。

認証・認可を図で確認

  • 認証 … メール・パスワードや OAuth などで「誰であるか」を確かめる
  • 認可 … 確かめたユーザーが、どの行・どの操作 をしてよいかを決める

フロントエンドで「ログイン済みならボタンを表示する」だけでは、認可は担保できません。
ブラウザから直接 API を叩かれた場合、UIの制御はすり抜けられるため、DBのRLSを最も重要な防波堤 として私は設計しています。

Supabase ではどこまで担保している?

SupabaseのAuthは、主に認証を担います。
ログイン・サインアップ、OAuth 連携、JWT(アクセス用トークン)の発行、セッション管理などです。

ただし、Auth が関わるのは認証だけではありません。
発行された JWT には、ユーザー ID などの情報が入っています。この情報は後述する RLS のポリシーから auth.uid() として参照でき、認可の判断材料になります。

Supabase が用意してくれないのは、認可のルールそのものです。
たとえば「自分が登録したレシピだけ読める」「同じ家族グループのメンバーだけ編集できる」は、開発者が RLS ポリシーなどで設計します。

領域 Supabase が担う 開発者が担う
ユーザー登録・ログイン UI や UX の設計
JWT の発行・検証 トークンの扱い方の理解
auth.uid() などの判断材料の提供 ポリシーでの使い方の設計
テーブル単位のアクセス制御 仕組み(RLS)を提供 認可ルールそのものを書く
ビジネスルール
(家族共有・グループ共有など)
× ポリシー・RPC・アプリ側の整理

認証と判断材料は Supabase、認可のルールは自分と私は整理しました。

RLS とは何か

RLS(Row Level Security)は、PostgreSQL の機能です。
行単位 で SELECT / INSERT / UPDATE / DELETE を制限できます。

たとえば、recipes テーブルに「ログインユーザーは自分の行だけ 読める」というルールを DB に置けます。
アプリのコードやフロントのチェックをすり抜けられても、DB側で拒否できる のがポイントです。

ALTER TABLE recipes ENABLE ROW LEVEL SECURITY;

CREATE POLICY "Users can read own recipes"
ON recipes FOR SELECT
USING (auth.uid() = user_id);

auth.uid() は、リクエストに含まれる JWT から Supabase が取り出すユーザー ID です。
「誰でログインしているか」 は Supabase が教えてくれますが、「その人に何を許すか」 はポリシー側の責務です。

RLS を有効にすると何が起きるか

私が最初に誤解していたのが、「ポリシーをまだ書いていない状態」の扱いです。

テーブルの設定 結果
RLS が無効 RLSによる行単位のアクセス制御が行われない
RLS が有効・ポリシーなし すべて拒否される(安全側に倒れる)
RLS が有効・ポリシーあり 条件を満たす行だけ許可される

つまり、RLS を有効にすると まず全部が閉じます。そこから「誰に何を許すか」をポリシーで開けていく、という順番です。

危険なのはポリシーの書き忘れよりも、そもそも RLS を有効にしていないケースです。

USING と WITH CHECK

ポリシーの条件には2種類あります。すでにある行を見てよいかと、これから書き込む行が条件を満たしているかです。

条件 対象 主に使う操作
USING すでにテーブルにある行 SELECT / UPDATE / DELETE
WITH CHECK これから保存される行 INSERT / UPDATE

書き込み側を縛らないと、「他人の user_id を指定してデータを登録する」といった操作を止められません。
また、UPDATEUSINGWITH CHECK両方の観点での設計が必要になるため、どちらも設定していることを確認するのが重要です。

CREATE POLICY "Users can insert own recipes"
ON recipes FOR INSERT
WITH CHECK (auth.uid() = user_id);

API キーとログイン情報は別物

ここで混同しやすいのが、API キーログインしたユーザーの情報(JWT) の違いです。

種類 答えるもの 具体例
API キー 何が アクセスしているか このWebアプリ、このサーバー
JWT 誰が アクセスしているか ログイン中のユーザー

API キーはアプリの身分証、JWT は利用者の身分証、というイメージです。
同じ API キーを使っていても、ログイン前とログイン後では DB 側で別のロールとして扱われ、RLS の判定もそれに従います。

2種類のAPI キー

Supabase プロジェクトには、公開してよいキーとサーバー専用のキーがあります。

キー 置き場所 RLS 使いどころ
Publishable key ブラウザ・モバイル 適用される 通常のクライアントからの読み書き
Secret key サーバー専用 バイパスする 管理処理・バッチ(要・厳重管理)

以前は anon キー、service_role キーという名前でした。現在は Legacy API keys という扱いで、役割はそれぞれ Publishable key、Secret key に対応します。

Secret key は RLS を通りません。クライアントに埋め込むとRLSをバイパスし、プロジェクトのデータへ強い権限でアクセスできるため、クライアントに埋め込むとRLSで保護していたデータへの意図しないアクセスを許してしまう恐れがあります。

私の個人開発では、通常の読み書きは Publishable key と RLS の経路に寄せ、
Secret key は 本当に必要なサーバー処理だけ に限定する方針にしています。

よくあるアンチパターン

1. RLS を有効にしていない

RLS が無効なテーブルでは、PostgreSQL の通常の権限設定に基づいてアクセスが許可されます。SupabaseのData APIからアクセスするテーブルでは、RLSを有効にして意図したアクセス制御を行います。

2. フロントの表示制御だけで安心している

「未ログインなら画面を出さない」は UX であって、セキュリティの本体ではありません。
API は直接叩けるため、認可は DB またはサーバー側 に置く必要があります。

3. Secret key をクライアントに渡している

環境変数の命名ミスや、Next.js の NEXT_PUBLIC_ 付き変数への誤設定で、本番に鍵が露出した事例は後を絶ちません。
Secret key は サーバー実行環境だけ に閉じ込めます。

4. 意図した公開範囲を超えてUSING (true)を設定している

USING (true) は「全行を許可」という意味です。お知らせ一覧のように 全員に見せたいデータ なら、これが正解のこともあります。
問題になるのは、動かすことを優先して、本来は制限すべきテーブル にまで USING (true) を置いてしまう場合です。

5. RLS だけを見て安心している

RLS はテーブルへのアクセスを守りますが、データに触れる経路はほかにもあります。
ビュー、SECURITY DEFINER を付けた関数、Storage のバケットなどは、それぞれ別に設定を確認する必要があります。

本番公開前の確認事項

個人開発でも、最低限次を確認しています。

  • API から触るテーブルで RLS が 有効 になっているか
  • 要件上必要な操作に、過不足のないポリシー が定義されているか
  • 登録・更新のポリシーで WITH CHECK を書いているか
  • Secret key がクライアント側のコードに含まれていないか
  • 「他人の user_id の行」に アクセスできない ことを実際に試したか
  • ビューや関数、Storage など RLS 以外の経路 も確認したか

最後に

Supabase は認証まわりを大きく肩代わりしてくれますが、「ログインできた=安全」ではありません
認証で「誰か」を確かめ、その情報をもとに 認可のルールを DB まで落とし込む のが大切だと学べました。

私自身、RLS とアプリケーションコードの境界で何度も迷いましたが、
Supabase が担う部分 / 自分が設計する部分 を分けて整理すると、次に何を調べればよいかがはっきりしました。

参考リンク

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?