ログインできるのに保存されない ― データベースの「2つの鍵」GRANTとRLS
ログインは成功しているのに、データだけ保存されない。原因はデータベースの権限でした。テーブル単位の鍵GRANTと、行単位の鍵RLSの違いを、基本情報技術者試験のDCL・最小権限・多層防御と結びつけて整理します。
このサイトに Google ログインとスコア保存を付けたとき、「ログインはできるのに、スコアが保存されない」という不具合が起きました。画面には何も出ず、データベースを見ても1件も入っていません。原因はデータベースの権限でした。この記事では、そのとき分かった「2つの鍵」を整理します。サンプルのテーブル名や列名はすべてダミーです。
起きたこと
- Google ログイン:成功(名前も表示される)
- 保存ボタンを押す:画面上は何も起きない
- データベース:0件のまま
保存の結果を画面に出すように直したところ、次のエラーが見えました。
permission denied for table typing_scores(テーブル typing_scores を操作する権限がありません)
「ログインしている=何でもできる」ではありません。だれか分かったこと(認証)と、何をしてよいか(認可)は別の話です。
データベースの2つの鍵
今回使っている PostgreSQL(Supabase)では、データを触るまでに2つの関門があります。
| 鍵 | 決めること | たとえ | 足りないとき |
|---|---|---|---|
| GRANT(権限) | そのテーブルを操作してよいか | 建物の入館証 | permission denied のエラー |
| RLS(行レベルセキュリティ) | どの行を操作してよいか | 自分の部屋の鍵 | 読むと0件・書くとエラー |
入館証(GRANT)がなければ建物に入れません。入館証があっても、部屋の鍵(RLS)が合わなければ他人の部屋には入れません。両方そろって初めて、自分のデータを読み書きできます。
まずテーブルを作る
-- メモを入れるテーブルを作る(DDL)
CREATE TABLE memos (
id BIGSERIAL PRIMARY KEY,
user_id UUID NOT NULL,
body TEXT NOT NULL
);部屋の鍵:RLS
RLS を有効にすると、ルール(ポリシー)に合う行しか見えなくなります。下のルールは「user_id がログインしている本人の行だけ」という意味です。
-- 行レベルセキュリティを有効にする
ALTER TABLE memos ENABLE ROW LEVEL SECURITY;
-- 「自分の行だけ」読める・書けるルール(ポリシー)
CREATE POLICY own_rows ON memos
FOR ALL
USING (user_id = auth.uid())
WITH CHECK (user_id = auth.uid());USING は「読む・変える・消すときにどの行が対象になるか」、WITH CHECK は「書き込む行がルールに合っているか」を決めます。
入館証:GRANT
今回の原因はこちらでした。SQL でテーブルを作ると、ログインした人のロール(authenticated)にテーブルの権限が付いていないことがあります。RLS のルールを正しく書いていても、入館証がないので建物に入れません。
-- ログインした人のロールに、テーブルを操作する権限を渡す(DCL)
GRANT SELECT, INSERT, UPDATE, DELETE ON memos TO authenticated;
-- 取り上げるときは REVOKE
REVOKE DELETE ON memos FROM authenticated;見落としやすい点:RLS は「黙って0件」
GRANT が足りないときは、はっきりエラーになります。ところが RLS のルールに合わないときは、読み込みはエラーにならず、0件が返るだけです。「データが無い」のか「見えないだけ」なのか、見た目では区別できません。
- エラーが出る → まず GRANT(テーブルの権限)を疑う
- エラーは出ないのに0件 → RLS のルール(
USINGの条件)を疑う - 書き込みで
violates row-level security policy→WITH CHECKの条件に合っていない(例:user_idを入れ忘れている)
どちらにしても、結果を画面やコンソールに出すのが一番の近道でした。何も表示しない作りだと、原因の手がかりが何も残りません。
基本情報技術者試験とのつながり
SQL の3つの種類
| 種類 | 役割 | 例 |
|---|---|---|
| DDL(データ定義言語) | 入れ物を作る・変える | CREATE ALTER DROP |
| DML(データ操作言語) | 中身を操作する | SELECT INSERT UPDATE DELETE |
| DCL(データ制御言語) | 権限を渡す・取り上げる | GRANT REVOKE |
今回の不具合は「DDL でテーブルを作ったが、DCL を流していなかった」と言い換えられます。
最小権限の原則
必要な人に、必要な操作だけを渡す考え方です。ログインしていない人(anon)には何も渡さず、ログインした人にも「自分の行だけ」に絞る。GRANT で操作の種類を、RLS で行の範囲を絞っています。
多層防御
1つの守りが破られても、次の守りで止める考え方です。サイトに置く鍵は「公開してよい鍵」だけにし、秘密の鍵はサーバー側から出さない。その上で、データベースでも GRANT と RLS の2段で守る。どれか1つが漏れても、全部は開かない形にしておきます。
チェックリスト
- テーブルを作ったら
ENABLE ROW LEVEL SECURITY - 「自分の行だけ」のポリシーを作る(
USINGとWITH CHECK) - 使うロールに
GRANTする(必要な操作だけ) - 保存の結果(成功・エラー文)を画面かコンソールに出す
- ログインしていない状態でも試し、読めないことを確かめる
この記事の用語
言葉を選ぶと「☆ 単語を保存」が出ます。ここで保存した言葉は、単語帳の「用語」に入ります。
- 認証:だれなのかを確かめること(ログイン)
- 認可:何をしてよいかを決めること
- ロール:権限をまとめて持たせる役割。ここでは anon と authenticated
- ポリシー:RLS で「どの行を許すか」を書いたルール
- 最小権限の原則:必要な権限だけを渡す
- 多層防御:守りを何重にも重ねる