Firestore の Security Rule を理解する
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 以外のルール
read
は get
と list
を内包しており、
write
は create
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内を検索することができます。
下の例では、
- create:
users
コレクションにログイン中のユーザーIDが存在したら許可 - delete:
users
該当するユーザーが管理権限を持っていたら許可
といった感じです。
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
デバッグがしづらく、ハマったら一度頭を冷やさないと永遠と時間が取られるのが難点ですね。良いデバッグ方法があったら是非教えてくださいー。
それでは。