IT基礎

ログインできるのに保存されない ― データベースの「2つの鍵」GRANTとRLS

ログインは成功しているのに、データだけ保存されない。原因はデータベースの権限でした。テーブル単位の鍵GRANTと、行単位の鍵RLSの違いを、基本情報技術者試験のDCL・最小権限・多層防御と結びつけて整理します。

このサイトに Google ログインとスコア保存を付けたとき、「ログインはできるのに、スコアが保存されない」という不具合が起きました。画面には何も出ず、データベースを見ても1件も入っていません。原因はデータベースの権限でした。この記事では、そのとき分かった「2つの鍵」を整理します。サンプルのテーブル名や列名はすべてダミーです。

起きたこと

保存の結果を画面に出すように直したところ、次のエラーが見えました。

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 のルールと GRANT をセットで書く。今はこれを作業のチェックリストに入れています。

見落としやすい点:RLS は「黙って0件」

GRANT が足りないときは、はっきりエラーになります。ところが RLS のルールに合わないときは、読み込みはエラーにならず、0件が返るだけです。「データが無い」のか「見えないだけ」なのか、見た目では区別できません。

どちらにしても、結果を画面やコンソールに出すのが一番の近道でした。何も表示しない作りだと、原因の手がかりが何も残りません。

基本情報技術者試験とのつながり

SQL の3つの種類

種類役割例
DDL(データ定義言語)入れ物を作る・変えるCREATE ALTER DROP
DML(データ操作言語)中身を操作するSELECT INSERT UPDATE DELETE
DCL(データ制御言語)権限を渡す・取り上げるGRANT REVOKE

今回の不具合は「DDL でテーブルを作ったが、DCL を流していなかった」と言い換えられます。

最小権限の原則

必要な人に、必要な操作だけを渡す考え方です。ログインしていない人(anon)には何も渡さず、ログインした人にも「自分の行だけ」に絞る。GRANT で操作の種類を、RLS で行の範囲を絞っています。

多層防御

1つの守りが破られても、次の守りで止める考え方です。サイトに置く鍵は「公開してよい鍵」だけにし、秘密の鍵はサーバー側から出さない。その上で、データベースでも GRANT と RLS の2段で守る。どれか1つが漏れても、全部は開かない形にしておきます。

チェックリスト

  1. テーブルを作ったら ENABLE ROW LEVEL SECURITY
  2. 「自分の行だけ」のポリシーを作る(USING と WITH CHECK)
  3. 使うロールに GRANT する(必要な操作だけ)
  4. 保存の結果(成功・エラー文)を画面かコンソールに出す
  5. ログインしていない状態でも試し、読めないことを確かめる

この記事の用語

言葉を選ぶと「☆ 単語を保存」が出ます。ここで保存した言葉は、単語帳の「用語」に入ります。

基本情報まとめで復習する →

← 記事一覧へ戻る