Cloud Firestore セキュリティ ルールをカスタマイズする

Cloud Firestore セキュリティ ルールは、Cloud Firestore に格納されているデータへの読み取りおよび書き込みアクセス権を持つユーザーを決定します。また、スキーマの検証と参照整合性を維持し、ビジネス ロジックの制約を適用します。

階層データを操作する

Cloud Firestore では、コレクション、ドキュメント、およびサブコレクションの階層でデータが保存され、階層深くの複数のレイヤで構成されるデータの保護が必要になることがよくあります。したがって、この階層データを適切に操作する方法を理解することが重要です。

Cloud Firestore では、/databases/データベース名/documents で始まるパスにすべてのドキュメントが保存されます。一般に、このパスは Cloud Firestore を使用するクライアントには開示されません。クライアントによって employees/stanley ドキュメントにデータが書き込まれることもありますが、Cloud Firestore セキュリティ ルールの観点では、この書き込みは databases/データベース名/documents/employees/stanley で行われることになります。

完全一致検索

場合によっては、Cloud Firestore で特定のドキュメントに対してルールを指定することもあります。その場合は、次の例に示すように、該当するドキュメントのフルパスを記述します。

service cloud.firestore {
  // An exact match for the Serenity spaceship in our database
  match /databases/{database}/documents/spaceships/serenity {
    // Rules go here...
  }
  // An exact match for an employee named Stanley
  match /databases/{database}/documents/employees/stanley {
    // Rules go here...
  }
}

検索のネスト

特定のドキュメントやコレクションのパスが長いと、時間とともに記述するのが面倒になってきますので、match 句を別の match 句の中に追加することによって、検索をネストできます。

service cloud.firestore {
  match /databases/{database}/documents/spaceships/serenity {
      // Rules written in this section would apply to the document located at
      // /databases/{database}/document/spaceships/serenity
    match /crew/jayne {
      // Rules written in this section would apply to the document located at
      // /databases/{database}/document/spaceships/serenity/crew/jayne
    }
  }
}

検索をネストする場合は、/databases/{database}/documents/ を外側の match として使用し、その中にプロジェクト固有のセキュリティ ルールを追加するのが一般的です。

service cloud.firestore {
  match /databases/{database}/documents {
    match /spaceships/serenity {
      // Rules go here for the "Serenity" spaceship
      match /crew/jayne {
        // Rules go here for the "Jayne" crew member
      }
    }
    match /employees/stanley {
      // Rules go here for employees named Stanley
    }
  }
}

ルールはカスケードされない

Cloud Firestore のセキュリティ ルールは、=** ワイルドカードを明示的に使用しない限り、カスケードされません(下記参照)。つまり、コレクション内のドキュメントに対してセキュリティ ルールを指定した場合、それらのルールはそのドキュメントにのみ適用され、そのドキュメントのサブコレクション内のドキュメントには適用されません。

次の例では、セキュリティ ルールは /spaceships/serenity ドキュメントには適用されますが、/spaceships/serenity/crew/hoban ドキュメントには適用されません(したがって、デフォルトでは誰もそのクルーメンバーにアクセスできません)。

service cloud.firestore {
  match /databases/{database}/documents {
    match /spaceships/serenity {
      // Rules go here for the Serenity spaceship.
      // Right now, there are NO rules allowing access to documents in the
      // crew subcollection.
    }
  }
}

コレクションのルールは、そのコレクション内のドキュメントには適用されません。セキュリティ ルールをドキュメント レベルではなくコレクション レベルで記述することはまれであり、またおそらく間違いでもあります。

次の例において、デベロッパーの意図は spaceships コレクションを公開することだったと思われますが、実際には、ドキュメント レベルでルールが記述されていないため、spaceships コレクション内のドキュメントを誰も読み取ることができません。

service cloud.firestore {
  match /databases/{database}/documents {
    // This is probably a mistake
    match /spaceships {
      allow read;
      // In spite of the above line, a user can't read any document within the
      // spaceship collection.
    }
  }
}

ワイルドカードの使用

実際には特定のコレクションだけでなく、コレクション内のすべてのドキュメントに適用されるルールを記述する必要がある場合がほとんどです。そのような状況では、ワイルドカードを使用して、パス内のすべての要素を指定できます。ワイルドカードを作成するには、文字列を中かっこで囲みます。

次の例では、下に記述されたセキュリティ ルールは、spaceships コレクション内のすべてのドキュメントに適用されます。

service cloud.firestore {
  match /databases/{database}/documents {
    match /spaceships/{spaceship} {
      // Rules go here...
    }
  }
}

ワイルドカードと一致するパス コンポーネントの値は、中かっこ内の文字列に割り当てられます。たとえば、上記のルールセットでは、ユーザーが spaceships/enterprise ドキュメントに書き込もうとした場合、spaceship の値は "enterprise" となり、その値はセキュリティ ルールで spaceship 変数を参照することによって後からアクセスできます。

ただし、上記の例で記述されたルールは、spaceships コレクション内のドキュメントにのみ適用されますので注意してください。これらのルールがすべてのサブコレクションにも適用されるようにする場合は、再帰的検索が必要です。

ワイルドカードを使用した再帰的検索

ワイルドカード検索の最後に =** を追加すると、そのレベルのパス要素だけでなく、下位にあるすべてのドキュメントまたはサブコレクションも再帰的に検索されます。たとえば、次のセキュリティ ルールでは、追加されたルールは、spaceships/serenity ドキュメントと spaceships/enterprise/crew/troi ドキュメントの両方に適用されます。

service cloud.firestore {
  match /databases/{database}/documents {
    match /spaceships/{spaceship=**} {
      // Rules go here...
      // These rules would also apply to all documents in the "spaceship"
      // collection, as we add documents in the "crew" subcollection.
    }
  }
}

前の例において、spaceship ワイルドカード変数に割り当てられる文字列は、そのレベルのパス要素だけではなく、一致するパスセグメント全体であることに注意してください。

たとえば、クライアントが spaceships/enterprise/crew/troi ドキュメントを更新しようとした場合、spaceship の値は enterprise/crew/troi になります。ワイルドカードで spaceship ドキュメントの名前だけを検索する場合は、次のようにセキュリティ ルールを再構築する必要があります。

service cloud.firestore {
  match /databases/{database}/documents {
    match /spaceships/{spaceship} {
      // Rules added here would apply to documents in the "spaceships"
      // collection.
      // The value of "spaceship" as captured by the wildcard  would just be
      // the name of the spaceship document
      match /{allChildren=**} {
        // Rules added here would apply to documents in any subcollections
      }
    }
  }
}

上記の例は、サブコレクションに一致するセキュリティ ルールを記述しているかのように見える状況の一例です。しかし、実際には =** ワイルドカードを使用しているため、これらのルールはそのサブコレクション内のドキュメントにも適用されます。

複数のルールが同じドキュメントに一致する場合

複数のルールが同じドキュメントに一致するという状況が発生する場合があります。このような場合、オペレーションはいずれかのルールによって許可されている場合にのみ実行できます。

たとえば、次のルールセットを考えると、1 つ目のルールでは、ログインしたユーザーに対してのみ spaceship サブコレクション内のデータの読み取りを許可し、2 つ目のルールでは、一般ユーザーに publicData サブコレクション内のすべてのドキュメントの読み取りを許可しています。

service cloud.firestore {
  match /databases/{database}/documents {
    match /spaceships/{spaceship} {
      // Rules would go here for the 'spaceship' documents
      match /{allChildren=**} {
         // Only signed in users can read this data
         allow read: if request.auth.uid != null;
      }
      match /publicData/{publicDocs} {
         // Make this publicly readable
         allow read;
      }
    }
  }
}

この場合、少なくとも 1 つのルールによって許可されていることから、ログインしていないユーザーも spaceships/pegasus/publicData/manifest ドキュメントにアクセスできます。

セキュリティ ルールを評価する

Cloud Firestore セキュリティ ルールはすべて、アクションを許可するかどうかを決定するブール式の形式を取ります。デフォルトの動作は false です。動作を明示的に許可するルールが存在しない場合、そのアクションは許可されません。

ルールの条件として単に true または false を指定することによって、特定のアクションを明示的に許可または禁止する場合があります。

service cloud.firestore {
  match /databases/{database}/documents {
    match /spaceships/{spaceship}/publicData/{public} {
      // All data is publicly readable; nobody can write to it
      allow read;
    }
    match /users/{user}/logs/{log} {
      // All data is writable; nobody can read it
      allow write;
      // Technically, this line isn't necessary, but it's a useful way
      // of making your intentions known.
      allow read: if false;
    }
    match /vault/{superSecret=**} {
      // Nobody can read or write anything in the vault
    }
  }
}

条件付きロジック

ルールを true または false に明示的に設定する方が役立つこともありますが、多くの場合、特定の基準を満たす場合に条件に応じてアクションを許可するルールを記述することが必要になります。

Cloud Firestore セキュリティ ルールにはさまざまな組み込み関数が含まれており、これを使用して、受信したリクエストに属するデータや、データベースにすでに存在するデータを評価できます。

使用可能な関数の全リストについては、リファレンス ドキュメントをご覧ください。ここでは、便利でよく使用されるメソッドをいくつか紹介します。

  • == および != を使用して、数値と文字列の両方の等価チェックを行います。
  • == null または != null を使用して、ドキュメント内にフィールドまたは値が存在するかどうかを確認します。
  • is を使用して、データが特定の型であることを確認します。確認できるデータ型は、stringintfloatboolnulltimestamplistmap です。
  • in を使用して、値がリストまたはマップ内にあることを確認します。
  • get() 関数を使用して、ドキュメントをマップに変換します。
  • exists() を使用して、データベースにドキュメントが存在するかどうかを確認します。

マップの操作

Cloud Firestore セキュリティ ルールで扱うデータの多くは、マップ形式、つまり Key-Value ペアのコレクションです。マップ内のフィールドにアクセスするには、ドット表記または角かっこを使用します。角かっこを使用する場合は、評価しているフィールドの名前を二重引用符で囲む必要があります。

// These two rules are the same, and are requesting the 'displayName' field of
// the 'auth' token map that's part of the 'request' variable.
allow read: if request.auth.token.displayName == "Malcolm Reynolds";
allow read: if request["auth"]["token"]["displayName"] == "Malcolm Reynolds";

request 変数

request 変数は、クライアントから受信したリクエストに関する情報を含むマップです。フィールドの全リストについてはリファレンス ドキュメントをご覧ください。よく使用されるフィールドは authresource の 2 つで、前者は、Firebase Authentication から取得した現在ログインしているユーザーに関する情報を含み、後者は送信されたドキュメントに関する情報を含みます。

request オブジェクトの一般的な使用例は次のとおりです。

  • request.auth が null でないことを確認して、ユーザーがログインしていることを確認します。
  • request.auth.uid の値を使用して、Firebase Authentication によって確認されたユーザーの ID を取得します。
  • request.resource.data.<キー> を使用して、ドキュメントの書き込みのために送信された値が特定の書式を満たしていることを確認します。これは、データベース内でデータ検証を実行する最も一般的な方法です。
service cloud.firestore {
  match /databases/{database}/documents {
    match /bulletinBoard/{note} {
      // Anybody can read these messages, as long as they're signed in.
      allow read, write: if request.auth != null;
    }
    match /users/{userID}/myNotes/{note} {
      // Anybody can write to their own notes section
      allow read, write: if request.auth.uid == userID;
    }
    match /spaceships/{spaceship} {
      allow read;
      // Spaceship documents can only contain three fields -- a name, a catchy
      // slogan, and cargo capacity greater than 6500.
      // Only these three fields are allowed, and this will evaluate to false
      // if any of these fields are null.
      allow write: if request.resource.data.size() == 3 &&
                   request.resource.data.name is string &&
                   request.resource.data.slogan is string &&
                   request.resource.data.cargo is int &&
                   request.resource.data.cargo > 6500;
    }
  }
}

上記の 2 番目のルール式では、userID 変数はデータパス自体のワイルドカード式から取得されていることに注意してください。ワイルドカードから変数を抽出する方法については、下記を参照してください。

resource 変数

resource 変数は request.resource 変数と似ていますが、データベースにすでに存在するドキュメントを参照する点が異なります。読み取りオペレーションの場合は読み取るドキュメント、書き込みオペレーションの場合は更新または変更する古いドキュメントを参照します。

resource オブジェクトと request.resource オブジェクトはどちらもマップです。どちらにもドキュメント名を示す __name__ プロパティと、すべてのユーザー定義プロパティを宣言する data プロパティが含まれます。これらのマップの data プロパティ内のキーにアクセスすることによって、参照されるドキュメントに属するさまざまなフィールドの値を取得できます。たとえば、resource.data.kesselRun は、resource 変数で参照されるドキュメント内の kesselRun フィールドの値にマップされます。

次に、resource オブジェクトを使用した例をいくつか示します。

service cloud.firestore {
  match /databases/{database}/documents {
    match /bulletinBoard/{note} {
      // You can read any document that has a custom field named "visibility"
      // set to the string "public" or "read-only"
      allow read: if resource.data.visibility in ["public", "read-only"];
    }
    match /products/{productID}/reviews/{review} {
      allow read;
      // A user can update product reviews, but they can't change the headline.
      // Also, they should only be able to update their own product review,
      // and they still have to list themselves as an author.
      allow update: if request.resource.data.headline == resource.data.headline
                    && resource.data.authorID == request.auth.uid
                    && request.resource.data.authorID == request.auth.uid;
  }
}

ワイルドカードから作成された変数の使用

ワイルドカードを含むデータベース パスを使用してルールを一致させる際に、中かっこ内に含まれる文字列に一致する名前の変数が作成されます。その後、セキュリティ ルールでその変数を参照できるようになります。

このようなタイプのワイルドカードの一般的な使用方法の 1 つは、userID ごとにドキュメントを保存し、ユーザーが自分の userID と一致する ID を持つドキュメントにのみアクセスできるようにすることです。

service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{userIDFromWildcard} {
      // Users can only edit documents in the database if the documentID is
      // equal to their userID
      allow read, write: if request.auth.uid == userIDFromWildcard;
    }
  }
}

現在データベースにあるドキュメントの評価

resource 変数を使用して、アクセスしようとしている現在のドキュメントを評価できますが、データベースの他の部分にある他のドキュメントを評価したい場合もあります。

これを行うには、データベース内のドキュメント パスを指定して get() 関数を呼び出します。これにより、現在のドキュメントが取得され、resource オブジェクトと同様に、data プロパティを使用してカスタム フィールドにアクセスできるようになります。

この機能の一般的な使用方法の 1 つは、アクセス制御リストを評価し、現在のユーザーの userID がそのリストに含まれるかどうかを確認することです。

また、exists() 関数を使用して、ドキュメントがすでに存在するかどうかを確認することもできます。

exists 関数または get 関数にパスを入力する際は、引用符を含めません。パス名に変数を含める場合は、変数をかっこで囲み、その前にドル記号を付ける必要があります。

service cloud.firestore {
  match /databases/{database}/documents {
    match /games/{game}/playerProfiles/{playerID} {
      // Every "game" document has the userID of a referee, who is allowed to
      // alter player profiles.
      allow write: if get(/databases/$(database)/documents/games/$(game)).data.referee == request.auth.uid;

      // All players in a game are allowed to view the player profiles of any
      // other player.
      allow read: if exists(/databases/$(database)/documents/games/$(game)/playerProfiles/$(request.auth.uid));
    }
    match /guilds/{guildID}/bulletinBoard/{post} {
       // Assume our guild document includes a "users" field, which itself is
       // a map consisting of a player ID and their role. For example:
       // {"user_123": "Member", "user_456": "Probation", "user_789": "Admin"}
       //
       // A player can write to the bulletin board if they're listed in the
       // guild's "users" map field as a "Member" or "Admin"
       allow write: if get(/databases/$(database)/documents/guilds/$(guildID)).data.users[(request.auth.uid)] in ["Admin", "Member"];
    }
  }
}

コレクションでは get() 関数と exists() 関数はいずれも使用することができません

カスタム関数の記述

Cloud Firestore セキュリティ ルールのカスタム関数を記述することができます。関数は単一の return ステートメントのみで構成します。

ドキュメントとそのサブコレクションにルールを適用する場合、同じセキュリティ ルールを何度も記述することになりがちですが、代わりにカスタム関数を使用することでその必要がなくなります。

関数はルール内のどこでも宣言できます。一致ブロック内から宣言すると、関数はそのブロック内で定義されているすべてのワイルドカード変数にアクセスできます。すべての関数は、request 変数と resource 変数も使用できます。

たとえば、次のルールの代わりに...

service cloud.firestore {
  match /databases/{database}/documents {
    match /projects/{projectID} {
      // A user can update a project if they're listed in the project's
      // "members" array. Or if the project is a "all-access" project.
      allow update: if request.auth.uid in get(/databases/$(database)/documents/projects/$(projectID)).data.members ||
                     get(/databases/$(database)/documents/projects/$(projectID)).data.accessType == "all-access";
      match /{allChildren=**} {
        // The same thing holds true for subcollections of this project
        allow update: if request.auth.uid in get(/databases/$(database)/documents/projects/$(projectID)).data.members ||
                      get(/databases/$(database)/documents/projects/$(projectID)).data.accessType == "all-access";
      }
    }
  }
}

次のように記述できます。

service cloud.firestore {
  match /databases/{database}/documents {
    match /projects/{projectID} {

      function canUserUpdateProject() {
        return request.auth.uid in get(/databases/$(database)/documents/project/$(projectID)).data.members ||
         get(/databases/$(database)/documents/projects/$(projectID)).data.accessType == "all-access";
      }

      allow update: if canUserUpdateProject();
      match /{allChildren=**} {
        allow update: if canUserUpdateProject();
      }
    }
  }
}

または


service cloud.firestore {
  match /databases/{database}/documents {
    match /projects/{projectID} {

      function isProjectAllAccess() {
        return get(/databases/$(database)/documents/projects/$(projectID)).data.accessType == "all-access";
      }
      function isUserOfficialMember() {
        return request.auth.uid in get(/databases/$(database)/documents/project/$(projectID)).data.members;
      }
      function canUserUpdateProject() {
        return isUserOfficialMember() || isProjectAllAccess();
      }

      allow update: if canUserUpdateProject();
      match /{allChildren=**} {
        allow update: if canUserUpdateProject();
      }
    }
  }
}

書き込みの保護

Cloud Firestore には、createupdatedelete の 3 種類の書き込みオペレーションがあります。これらは、クライアント ライブラリの set()add()update()remove()transaction() メソッドに対応しています。write オペレーションを実行すると、これらの処理をすべて行うことができます。

service cloud.firestore {
  match /databases/{database}/documents {
    // Writes are divided into create, update, and delete operations
    match /myCollection/myDocument {
      allow create, update, delete: if <condition>;
    }

    // This is equivalent to using the write operation
    match /myCollection/myDocument {
      allow write: if <condition>;
    }
  }
}

書き込みルールは主に、ドキュメントに書き込まれるコンテンツの検証に使用されます。たとえば、特定のフィールドが存在し、他のフィールドが存在しないことを確認したり、フィールドが特定のタイプであることを確認したりします。

Cloud Firestore セキュリティ ルールの request.resource プロパティで、書き込まれるドキュメントが条件を満たしているかどうか検証します。

service cloud.firestore {
  match /databases/{database}/documents {
    match /rooms/{roomId} {
      match /messages/{messageId} {
        // A message must only contain two string fields, named "name" and "text"
        allow write: if request.resource.data.keys().hasAll(["name", "text"])
                     && request.resource.data.size() == 2
                     && request.resource.data.name is string
                     && request.resource.data.text is string;
    }
  }
}

次のコードを使用すると、ルールをテストできます。

// Add new messages to our room "chat"
var messagesRef = db.collection("rooms").doc("chat").collection("messages");

// This will succeed, because it meets our criteria!
messagesRef.add({
  name: "River Tam",
  text: "My food is problematic."
});

// This will fail, because it isn't a valid message according to our rule
//   "text" is a floating point number
//   "timestamp" is an extra field
messagesRef.add({
  name: "River Tam",
  text: 3.141592765,
  timestamp: Date.now()
});

ルールでオペレーションが許可されないと、書き込みは失敗し、エラーが返されます。

データを更新するときに、書き込まれるドキュメント(request.resource)と既存のドキュメント(resource)を比較できます。

service cloud.firestore {
  match /databases/{database}/documents {
    match /rooms/{roomId} {
      match /messages/{messageId} {
        // Existing and potential values must be equal
        allow update: if request.resource.data.name == resource.data.name;
    }
  }
}

次のコードで結果を確認できます。

// This will fail, because the rule doesn't allow this change
messagesRef.doc("some-uuid").update({
  name: "Jubal Early"
});

読み取りの保護

Cloud Firestore の読み込みとクエリには、getlist という 2 種類のオペレーションがあります。これらは、クライアント ライブラリの get()where().get() メソッドに対応しています。read オペレーションを実行すると、この両方の処理が行われます。

service cloud.firestore {
  match /databases/{database}/documents {
    // Reads are divided into get and list operations
    match /myCollection/myDocument {
      allow get, list: if <condition>;
    }

    // This is equivalent to using the read operation
    match /myCollection/myDocument {
      allow read: if <condition>;
    }
  }
}

ドキュメントの読み取り

既知のフィールドを持つドキュメントのコレクションを照会するため、「フィールドベースのクエリ」とも呼ばれます。Cloud Firestore セキュリティ ルールを使用すると、個々のドキュメントを不正な読み取りから保護することができます。たとえば、特定のユーザーがメッセージだけ参照できるように制限するには、次のようにします。

service cloud.firestore {
  match /databases/{database}/documents {
    match /rooms/{roomId} {
      match /messages/{messageId} {
        // Allow reads if the name is "Inara Serra"
        allow read: if resource.data.name == "Inara Serra";
    }
  }
}
// Read a particular message from our chat room
messagesRef.doc("some-message-owned-by-inara").get().then(function(document) {
  // Query succeeds, message should contain "name" and "text" properties
  var message = document.data();
});

層が少ないクエリ

層が少ないクエリは、コレクション内で特定のフィルタに一致するすべてのドキュメントをフェッチします。Cloud Firestore セキュリティ ルールの興味深い特性は、個々のドキュメントの読み込みを禁止する上記のルールをそのドキュメントが含まれるコレクションのクエリにも同様に適用できることです。

クエリのルールチェックを実行するとき、Cloud Firestore セキュリティ ルールは、クエリの実行前にユーザーのアクセス権を確認します。クエリでユーザーがアクセスできない結果が返されると、クエリ全体が失敗し、Firestore がエラーを返します。

// Read all messages by Inara
messagesRef.where("name", "==", "Inara Serra").get().then(function(documentSet) {
  documentSet.forEach(function(document) {
    // Query succeeds, message should contain "name" and "text" properties
    var message = document.data();
  });
});

次のステップ

Cloud Firestore セキュリティルールの詳細については、次のガイドを参照してください。

フィードバックを送信...

ご不明な点がありましたら、Google のサポートページをご覧ください。