#はじめに
Firestoreのセキュリティルールについて備忘録としてまとめます。
#セキュリティルールとは
このルールを設定することによって保管したデータのセキュリティを確保
でき、悪意のあるユーザーからデータを守ること
ができます。
単純なルールだけでなく、アプリの構造と動作に応じて複雑なルールも自分で設定できます。
Cloud Firestore セキュリティ ルールを使ってみる | Firebase
このセキュリティルールの設定方法は
・Firebaseコンソール
の2種類から設定できます。
要するにブラウザから直接ルールを編集
してルールを決めるか、自分のPCでファイルから設定を記述
してルールを決めるかですね。
因みに自分は、Firebaseコンソールから設定しています。
#セキュリティルールの基本的な構成
デフォルトでは以下のようになっています。
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if
request.time < timestamp.date(2021, 3, 18);
}
}
}
順にコードの意味を理解していきましょう。
##rules_version = '2';
バージョン
を指定しており、バージョン2
が最新となっています。
バージョンの指定がないと自動的にバージョン1
になります。
##service cloud.firestore
ルールを適用するプロダクト(サービス)
を指定しています。
他のプロダクトとの競合を防ぐための設定なので、1つ
しか設定できません。
Firebase CLIを使用してFirestoreとFirebase Storageの両方のルールを定義する場合は
それぞれ別のファイルでルールを保守
しなくてはいけません。
##match /databases/{database}/documents
ルールを適用するプロジェクト内でのFirestoreデータベース
を指定しています。
現在、Firestoreでは1つのプロジェクトに1つのデータベース
と決まっていて(default)
という名前が付いています。
Cloud Firestore セキュリティ ルールを構造化する | Firebase
##match /{document=**}
先ほども登場しましたが、matchステートメントを使用することでパス(場所)
を指定することが出来ます。
指定先はドキュメントのみ
しか指定できず、コレクションは指定できません
。
matchステートメントの次に記述されている括弧の部分はワイルドカード表記
と呼ばれます。
// ワイルドカード表記
{document=**}
この表記方法を用いれば任意のドキュメントを指定
することができます。
例えば、foodコレクションがあって、その中のドキュメントを指定するとします。
// foodコレクションの任意のドキュメントを指定する
// {}の中は自由に記述可能で、変数として使うこともできる
match /food/{meet} {
allow read: if resource.data.name == meet;
}
※resource.data.name "ドキュメントに格納されている全てのフィールドと値のマップを参照できる"
// 特定のドキュメントも指定できる
// しかし、fruitsドキュメント以外の読み書きは拒否される
match /food/fruits {
}
このようになります。
前後しますが、何度も登場している{document=**}は再帰ワイルドカード
と呼ばれ、普通のワイルドカードとは少し違います。
// 再帰ワイルドカード
{document=**}
// ワイルドカード
{meet}
これは例えば、各階層のサブコレクション全てにルールを適用したい場合
などに使用すると非常に便利です。
例えば、foodコレクションより下の階層全てにルールを適用したい場合だと下記のようになります。
// foodコレクション以下の全てのドキュメントに対してルールを適用する
match /food/{document=**} {
}
// 普通のワイルドカードだとfoodコレクション内のドキュメントしか指定できず、それより下の階層は指定できない
match /food/{document} {
}
以上を踏まえてデフォルトで記述されている下記のコードの意味を考えると、プロジェクト内全てのドキュメントに対してルールが適用される
という意味合いになります。
// プロジェクト内すべてのドキュメントに対してルールを適用する
match /{document=**} {
}
##allow read, write: if
この部分が実際にセキュリティルールを指定する所で読み書きの許可条件
を決めます。
分かりやすくするとこんな感じ↓
allow [アクセスタイプ(メソッド)]: if [条件];
allowステートメントの後に記述されているread
やwrite
なんかはアクセスタイプ(メソッド)
として機能しており、ユーザーに対して読み書きのアクセス権を付与
します。
大きく分けるとwrite
とread
の2つなのですが、ここから更に細分化
することが出来ます。
read "あらゆるタイプの読み取りリクエスト"
- get "単一のドキュメントを対象とした読み取りリクエスト"
- list "クエリとコレクションを対象とした読み取りリクエスト"
write "あらゆるタイプの書き込みリクエスト"
- create "新しいドキュメントの書き込み"
- update "既存のドキュメントへの書き込み"
- delete "データの削除"
これらのアクセスタイプ(メソッド)を、必要に応じて加えていく感じですね。
そして、ifの後には様々な条件
を書き足すことができます。
デフォルトだと、改行されていて分かりにくいですがrequest.time < timestamp.date(2021, 3, 18);
が条件ですね(後ほど説明)。
条件のよくある例として、Bool値
を記述して読み書きしてもいいかを決めます。
// 読み書きを許可する
allow read, write: if true;
// 読み書きを拒否する
allow read, write: if false;
デフォルトではBool値が省略されており、その場合は無条件でtrue
になります。
// 無条件でtrueが返される
allow read, write: if
##request.time < timestamp.date(2021, 3, 18);
先ほどのifの後に続く条件
です。
意味としては、2021/3/18の日付より過去なら読み書き可能
という感じになっています。
#Firebase Authenticationとの連携
FirestoreとFirebase Authentication
を連携して、より強固なセキュリティルールを設定することが出来ます。
セキュリティ ルールと Firebase Authentication | Firebase
※Firebase Authenticationがアプリにて実装されていることが大前提です。
##ユーザーを識別する
Firestoreにはユーザーを識別するために使用できる変数
がいくつか用意されています。
例えばログインプロバイダにメール/パスワード
を用いている場合は、
uid "リクエストしているユーザーに割り当てられたユーザーID"
auth.token.email "アカウントに関連付けられているメールアドレス"
などが使用できます。
他にも色々、変数が存在しますので公式ドキュメントを読んでみて下さい。
##ルールでユーザー情報を使用する
早速、セキュリティルールにユーザー情報を使用してみましょう。
下記では、usersコレクションの任意のドキュメントを指定しており、
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
// アカウントが存在し、auth.uid変数とリクエスト送信者のユーザーIDが一致していれば読み書きを許可する
allow read, write: if request.auth != null && request.auth.uid == userId;
}
}
}
という構成になっています。
##requst.auth != null
request.auth
は、Firebase Authenticationから取得した認証情報
を表しており、ここではアカウントが存在していれば
という意味になります。
##request.auth.uid == userId
この部分は、アカウントのuidとFirestoreに保存してあるユーザーIDを照合
して一致していれば読み書きを許可します。
match /users/{userId} {
allow read, write: if request.auth.uid == userId;
}
#関数
セキュリティルールには関数
を定義することができ、一連の条件をまとめる
ことが出来ます。
service cloud.firestore {
match /databases/{database}/documents {
// アカウントが存在していればtrueを返す関数
function isAuthentication() {
return request.auth.uid != null;
}
// このように複数の場所から条件を指定できる
match /cities/city {
allow read: if isAuthentication();
}
match /users/{user} {
allow read, write: if isAuthentication();
}
}
}
関数に引数
を割り当てることも可能です。
function isMe(userEmail) {
return request.auth.token.email == userEmail;
}
match /users/{userEmail} {
allow write, read: if isMe(userEmail);
}
#おわりに
とにかく公式ドキュメントを読もう。
次は、実際にFirestoreとFirebase Authenticationを連携してユーザーによって保存されているデータを分ける方法をまとめようと思います。
#参考記事
Cloud Firestore セキュリティ ルールを構造化する
Firestoreセキュリティルールを読み解く
Firestore セキュリティルール ~入門編~