Firestore の Security Rule を理解する

Shunsuke Sawada

firebaseは気軽に使えるデータベースですが、セキュリティルールはRDSなんかとは全然違うので、改めて勉強しました。尚、ドキュメントの内容を要約しただけになってます。

firebase プロジェクト

firebase cli でプロジェクトをつくっていることが前提です。
すでにつくっている場合でも下記コマンドで今回必要な設定ファイルを作成してれたりします。

1
$ firebase init

色々聞かれますが override? と聞かれたらもちろん No で。
今回必要なファイルは firebase.rules になります。

作成した後に、

1
$ firebase deploy --only firestore:rules

とすれば、ルールをアップロードしてくれます。
firebaseの管理画面で設定することも可能ですが、 gitレポジトリで管理したいし、エディタで書いた方が間違いがないと思うので、この方法で。

  

基本

大事なのは match 以下 だけ。
この例では posts コレクションの読み書きを全員に許可します。

js
1
2
3
4
5
6
7
service cloud.firestore {
  match /databases/{database}/documents {
    match /posts/ {
      allow read, write: if true;
    }
  }
}

 
シンプルでそのままなのですが、
:if xxx 以降が true であれば、 read, write を許可するという意味です。

1
allow read, write: if true;

  
よくやるのは、ユーザーがログインしていれば、書き込み許可。

1
allow write: if request.auth.uid != null;

  
この部分はおまじないです。
将来、複数のDBを持てるようになるかもしれないので、こういう構造にしてるそうです。
現状では、1つのプロジェクトに持てるDBは1つだけなので、ただの約束事として覚えましょう。

js
1
2
3
4
5
service cloud.firestore {
  match /databases/{database}/documents {
  ...
  }
}

 

read, write 以外のルール

readgetlist を内包しており、
writecreate update delete を含んでいます。
それぞれ別々に書くことももちろんできます。

  

ワイルドカード

単一リソースのワイルドカード

1
match /posts/{post}

これは posts 以下のすべてのドキュメントにマッチしますが、
posts 以下にコメントなどがネストされている場合は、コメントのサブコレクションにはマッチしません。

1
match /posts/{document=**}

これだと posts 以下なんでもマッチします。

  

複数マッチする場合

以下の例だと、最初のルールで、全員に対して読み書きを禁止しているのですが、下のルールで許可しています。
こういうふうに複数マッチする条件が設定されている場合、ひとつでも条件が true であれば、許可されます。上書きできるので、便利かもしれませんが、あまりやりすぎると管理が大変かと。

js
1
2
3
4
5
6
7
8
9
10
service cloud.firestore {
  match /databases/{database}/documents {
    match /posts/{post} {
      allow read, write: if false;
    }
    match /posts/{document=**} {
      allow read, write: if true;
    }
  }
}

  

条件を書く

セキュリティルールは、ここがメインですね。
:if xxx 以下の部分です。
どういう条件の時に、指定したアクションを許可するのかを設定していきます。

ログインしている時、書き込みを許可

js
1
2
3
    match /posts/{post} {
      allow read, write: if request.auth.uid != null;
    }

自分のプロフィールページにアクセスすることを許可

js
1
2
3
    match /users/{userId} {
      allow read: if request.auth.uid == userId;
    }

公開中の記事のみアクセスを許可

resource.data は DBに保存されている記事が入っています。

js
1
2
3
4
5
  match /databases/{database}/documents {
    match /posts/{post} {
      allow read: if resource.data.isPublished == true';
    }
  }

投稿記事のタイトルが空白でなければ書き込みを許可

こちらは投稿中で、まだDB内に存在しない場合。
add() なんかでデータ書き込みを試みている時ですね。
request.resource.data に自分が投げているリクエストの記事データが入っています。

js
1
2
3
4
    match /posts/{post} {
      allow create: if request.resource.data.title != null
                    && request.resource.data.title != '';
    }

   

他のデータを参照して許可するかどうか決める

ちょっと複雑になりますが、よく使います。
request / resource / auth だけだと判断できない場合は、DB内を検索することができます。
下の例では、

といった感じです。

js
1
2
3
4
    match /posts/{post} {
      allow create: if exists(/databases/$(database)/documents/users/$(request.auth.uid))
      allow delete: if get(/databases/$(database)/documents/users/$(request.auth.uid)).data.admin == true
    }

  

関数を定義してまとめる

1
if request.auth.uid != null;

なんかはとてもよく使うので、関数として定義しておくと便利です。

js
1
2
3
4
5
6
7
    function isSignedIn() {
      return request.auth.uid != null;
    }

    match /posts/{post} {
      allow read, write: if isSignedIn();
    }

ただし、関数は複雑なことはできません。
JavaScriptのように見えますが、別言語です。
先程の get() / exists() が使えたり resource / request にアクセスできます。
変数を定義したりはできません。ワンライナーでリターンしないといけないとのこと。

  

もっと複雑な権限管理をしたい場合

usersドキュメントにこんな感じのデータを用意して、
先程の関数を駆使してごにょごにょすると良いです。

js
1
2
3
4
5
6
7
  roles: {
    alice: "owner",
    bob: "reader",
    david: "writer",
    jane: "commenter"
    // ...
  }

詳しくはこちらを読むと、どういう処理なのか分かります。
https://firebase.google.com/docs/firestore/solutions/role-based-access

参考

今回のドキュメント
Secure Data in Cloud Firestore

セキュリティを変更したら、こちらも見ておくことをお勧めします。
じゃないとデータを検索した時にハマるかも。
Securely Query Data

resource の中身。
rules.firestore.Resource

  
デバッグがしづらく、ハマったら一度頭を冷やさないと永遠と時間が取られるのが難点ですね。良いデバッグ方法があったら是非教えてくださいー。

それでは。
  

Shunsuke Sawada

おすすめの記事

爆速でウェブアプリを公開する方法(無料でSSL独自ドメイン)/ React + Typescript + Cloud Function + Firebase Hosting
2
Google Cloud Platform で FireStore を使用して Rules が作成できない場合の対処
SlackのOAuthを使って独自アプリをインストールさせる
1