Firestoreセキュリティルール言語

サーバーが無い構成の防波堤

[[BaaS(Backend as a Service)]] では、アプリがデータベースを直接呼びます。間にサーバーが無いため、「この利用者はこのデータを読んでよいか」を判断する場所がありません。

その判断を担うのがセキュリティルールです。データベース側に置かれた宣言で、すべての読み書きがここを通ります。

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{userId} {
      allow read: if request.auth != null;
      allow write: if request.auth.uid == userId;    // 本人だけ書ける
    }
    match /tasks/{taskId} {
      allow read, write: if request.auth.uid == resource.data.ownerId;
    }
  }
}

クライアントのコードは信用できません。 アプリ側で「自分のデータだけ表示する」と書いても、通信を直接組み立てられれば他人のデータを要求できます。ルールはその要求を拒否する最後の砦です。

押さえるべき3つの値

値意味
request.auth誰が要求しているか。未ログインなら null
resource.data既にあるドキュメントの中身(読み取り・更新前の状態)
request.resource.dataこれから書こうとしている中身

resource と request.resource の取り違えが、最も多い誤りです。

// 所有者を変更させない
allow update: if request.auth.uid == resource.data.ownerId
              && request.resource.data.ownerId == resource.data.ownerId;

前半で「今の所有者本人か」を見て、後半で「所有者を書き換えていないか」を見ています。後半が無いと、自分のデータの所有者を他人に付け替えられます。

Row Level Securityとの違い

考え方は [[Row Level Security(RLS)]] と同じ「データ側で判定する」ですが、前提が違います。

FirestoreルールRLS(PostgreSQL)
書き方専用のルール言語SQLの述語
判定の単位ドキュメントのパス行
誰が接続するかクライアントが直接通常はサーバー
結合できない(別ドキュメントの参照はコスト付き)JOIN が使える

パス単位であることが設計に強く影響します。/users/{userId}/tasks/{taskId} のように所有関係をパスへ埋め込んでおくと、ルールが単純になります。逆に所有者をフィールドに持つ設計だと、判定のたびにドキュメントの中身を読むことになります。

アクセス制御の都合がデータ構造を決めるのがこのモデルの特徴です。

クエリとの関係

ルールはクエリの結果を絞り込みません。「読める分だけ返す」のではなく、要求全体を許可するか拒否するかです。

// これは失敗する: 全件取得の要求は、条件を満たさないドキュメントを含みうる
db.collection('tasks').get()

そのため、クライアント側のクエリにも同じ条件を書く必要があります。ルールとクエリの両方が揃って初めて動きます。「ルールを通したのに permission denied になる」の大半はこれが原因です。

実務での注意点

  • ルールのテストを書く — エミュレータで許可・拒否の両方を検証します。拒否されるべき要求が拒否されることを確かめるのが本題です
  • [[最小権限の原則]] から始める — 既定をすべて拒否にし、必要なものだけ開けます。開発中に全開放したまま公開する事故が最も多くあります
  • 公開範囲の広いコレクションを分ける — 誰でも読める情報と本人限定の情報を同じドキュメントに混ぜると、ルールで分離できません
  • ルールは課金対象の読み取りを発生させることがある — 他ドキュメントを参照する条件はコストが乗ります

関連技術とのつながり

  • [[BaaS(Backend as a Service)]] — サーバーを置かない構成。ルールが認可の主役になる
  • [[Row Level Security(RLS)]] — 同じ「データ側で判定する」考え方のRDB版
  • [[認証と認可]] — request.auth が認証、ルールが認可にあたる
  • [[最小権限の原則]] — 既定を拒否から始める設計指針
  • [[NoSQL]] — ドキュメント指向の構造がパス単位の判定と結びつく
Q: Firestoreセキュリティルールが必要な理由はどれ?
- [ ] データベースの性能を上げるため
- [x] クライアントが直接DBを呼ぶため、認可を判断するサーバーが間に無いから
- [ ] データを暗号化するため
解説: アプリ側のコードは信用できません。通信を直接組み立てられても拒否できる場所が要ります。

Q: `resource.data` と `request.resource.data` の違いはどれ?
- [x] 前者は既にある中身、後者はこれから書こうとしている中身
- [ ] 前者は読み取り用、後者は管理者用
- [ ] 同じものの別名
解説: 取り違えると、所有者フィールドの付け替えのような書き換えを許してしまいます。

Q: ルールを満たしているのに全件取得が拒否されるのはなぜ?
- [ ] インデックスが無いから
- [x] ルールは結果を絞り込まず要求全体を許可/拒否するため、クエリにも同じ条件が要るから
- [ ] 認証の有効期限が切れているから
解説: ルールとクエリの両方が揃って初めて動きます。