Firebase Summit のすべての発表内容に目を通し、Firebase を活用してアプリ開発を加速し、自信を持ってアプリを実行できる方法をご確認ください。 詳細

データを安全にクエリする

このページは、「セキュリティ ルールの構造化」「セキュリティ ルールの条件の記述」の概念に基づいて構築されており、Cloud Firestore セキュリティ ルールがクエリとどのように相互作用するかを説明します。セキュリティ ルールが記述可能なクエリにどのように影響するかを詳しく見ていき、セキュリティ ルールと同じ制約をクエリで確実に使用する方法について説明します。このページでは、 limitorderByなどのクエリ プロパティに基づいてクエリを許可または拒否するセキュリティ ルールを作成する方法についても説明します。

ルールはフィルターではありません

ドキュメントを取得するためのクエリを作成するときは、セキュリティ ルールはフィルターではないことに注意してください。クエリはすべてかゼロかです。時間とリソースを節約するために、Cloud Firestore は、すべてのドキュメントの実際のフィールド値ではなく、潜在的な結果セットに対してクエリを評価します。クライアントが読み取り権限を持っていないドキュメントをクエリが返す可能性がある場合、リクエスト全体が失敗します。

クエリとセキュリティ ルール

以下の例が示すように、セキュリティ ルールの制約に適合するようにクエリを記述する必要があります。

auth.uidに基づくドキュメントの保護とクエリ

次の例は、セキュリティ ルールによって保護されたドキュメントを取得するためのクエリを記述する方法を示しています。 storyドキュメントのコレクションを含むデータベースを考えてみましょう。

/stories/{ストーリーID}

{
  title: "A Great Story",
  content: "Once upon a time...",
  author: "some_auth_id",
  published: false
}

各ドキュメントには、 titleフィールドとcontentフィールドに加えて、アクセス制御に使用する作成authorフィールドとpublishedフィールドが格納されます。これらの例では、アプリがFirebase Authenticationを使用してauthorフィールドをドキュメントを作成したユーザーの UID に設定することを前提としています。 Firebase Authentication は、セキュリティ ルールのrequest.auth変数にも入力します。

次のセキュリティ ルールでは、 request.authおよびresource.data変数を使用して、各storyの読み取りおよび書き込みアクセスを作成者に制限します。

service cloud.firestore {
  match /databases/{database}/documents {
    match /stories/{storyid} {
      // Only the authenticated user who authored the document can read or write
      allow read, write: if request.auth != null && request.auth.uid == resource.data.author;
    }
  }
}

アプリに、ユーザーが作成したstoryドキュメントのリストをユーザーに表示するページが含まれているとします。次のクエリを使用してこのページにデータを入力できると思うかもしれません。ただし、セキュリティ ルールと同じ制約が含まれていないため、このクエリは失敗します。

無効: クエリの制約がセキュリティ ルールの制約と一致しません

// This query will fail
db.collection("stories").get()

現在のユーザーが実際にはすべてのstoryドキュメントの作成者であっても、クエリは失敗します。この動作の理由は、Cloud Firestore がセキュリティ ルールを適用するときに、データベース内のドキュメントの実際のプロパティではなく、潜在的な結果セットに対してクエリを評価するためです。セキュリティ ルールに違反するドキュメントがクエリに含まれる可能がある場合、クエリは失敗します。

対照的に、次のクエリは成功します。これは、 authorフィールドにセキュリティ ルールと同じ制約が含まれているためです。

Valid : クエリの制約がセキュリティ ルールの制約と一致する

var user = firebase.auth().currentUser;

db.collection("stories").where("author", "==", user.uid).get()

フィールドに基づいたドキュメントの保護とクエリ

クエリとルールの相互作用をさらに詳しく説明するために、以下のセキュリティ ルールは、 storiesコレクションの読み取りアクセスを拡張して、 publishedフィールドがtrueに設定されているstoryドキュメントをすべてのユーザーが読み取れるようにします。

service cloud.firestore {
  match /databases/{database}/documents {
    match /stories/{storyid} {
      // Anyone can read a published story; only story authors can read unpublished stories
      allow read: if resource.data.published == true || (request.auth != null && request.auth.uid == resource.data.author);
      // Only story authors can write
      allow write: if request.auth != null && request.auth.uid == resource.data.author;
    }
  }
}

公開されたページのクエリには、セキュリティ ルールと同じ制約が含まれている必要があります。

db.collection("stories").where("published", "==", true).get()

クエリ制約.where("published", "==", true)は、すべての結果に対してresource.data.publishedtrueであることを保証します。したがって、このクエリはセキュリティ ルールを満たし、データの読み取りが許可されます。

inおよびarray-contains-anyクエリ

ルールセットに対してinまたはarray-contains-anyクエリ句を評価する場合、Cloud Firestore は各比較値を個別に評価します。各比較値は、セキュリティ ルールの制約を満たす必要があります。たとえば、次のルールの場合:

match /mydocuments/{doc} {
  allow read: if resource.data.x > 5;
}

無効: クエリは、すべての潜在的なドキュメントに対してx > 5であることを保証しません

// This query will fail
db.collection("mydocuments").where("x", "in", [1, 3, 6, 42, 99]).get()

Valid : クエリは、すべての潜在的なドキュメントに対してx > 5であることを保証します

db.collection("mydocuments").where("x", "in", [6, 42, 99, 105, 200]).get()

クエリの制約の評価

セキュリティ ルールは、制約に基づいてクエリを許可または拒否することもできます。 request.query変数には、クエリのlimitoffset 、およびorderByプロパティが含まれています。たとえば、セキュリティ ルールは、取得するドキュメントの最大数を特定の範囲に制限しないクエリを拒否できます。

allow list: if request.query.limit <= 10;

次のルールセットは、クエリに課された制約を評価するセキュリティ ルールを記述する方法を示しています。この例では、次の変更を加えて以前storiesルールセットを拡張します。

  • ルールセットは、読み取りルールをgetlistのルールに分けます。
  • getルールは、単一ドキュメントの取得を公開ドキュメントまたはユーザーが作成したドキュメントに制限します。
  • list規則は、 getと同じ制限をクエリに適用します。また、クエリの制限をチェックし、制限のないクエリまたは制限が 10 を超えるクエリを拒否します。
  • ルールセットは、コードの重複を避けるためにauthorOrPublished()関数を定義します。
service cloud.firestore {

  match /databases/{database}/documents {

    match /stories/{storyid} {

      // Returns `true` if the requested story is 'published'
      // or the user authored the story
      function authorOrPublished() {
        return resource.data.published == true || request.auth.uid == resource.data.author;
      }

      // Deny any query not limited to 10 or fewer documents
      // Anyone can query published stories
      // Authors can query their unpublished stories
      allow list: if request.query.limit <= 10 &&
                     authorOrPublished();

      // Anyone can retrieve a published story
      // Only a story's author can retrieve an unpublished story
      allow get: if authorOrPublished();

      // Only a story's author can write to a story
      allow write: if request.auth.uid == resource.data.author;
    }

  }
}

コレクション グループのクエリとセキュリティ ルール

デフォルトでは、クエリは 1 つのコレクションに限定され、そのコレクションからのみ結果を取得します。コレクション グループ クエリを使用すると、同じ ID を持つすべてのコレクションで構成されるコレクション グループから結果を取得できます。このセクションでは、セキュリティ ルールを使用してコレクション グループ クエリをセキュリティで保護する方法について説明します。

コレクション グループに基づいてドキュメントを保護し、クエリを実行する

セキュリティ ルールでは、コレクション グループのルールを記述して、コレクション グループ クエリを明示的に許可する必要があります。

  1. rules_version = '2';あることを確認してください。ルールセットの最初の行です。コレクション グループ クエリには、セキュリティ ルール バージョン 2 の新しい再帰ワイルドカード{name=**}動作が必要です。
  2. match /{path=**}/ [COLLECTION_ID] /{doc}を使用して、コレクション グループのルールを記述します。

たとえば、 postsサブコレクションを含むforumドキュメントに編成されたフォーラムを考えてみましょう。

/forums/{forumid}/posts/{postid}

{
  author: "some_auth_id",
  authorname: "some_username",
  content: "I just read a great story.",
}

このアプリケーションでは、投稿を所有者が編集可能にし、認証されたユーザーが読めるようにします。

service cloud.firestore {
  match /databases/{database}/documents {
    match /forums/{forumid}/posts/{post} {
      // Only authenticated users can read
      allow read: if request.auth != null;
      // Only the post author can write
      allow write: if request.auth != null && request.auth.uid == resource.data.author;
    }
  }
}

認証されたユーザーは、任意の単一のフォーラムの投稿を取得できます。

db.collection("forums/technology/posts").get()

しかし、現在のユーザーにすべてのフォーラムでの投稿を表示したい場合はどうすればよいでしょうか?コレクション グループ クエリを使用して、すべてのpostsコレクションから結果を取得できます。

var user = firebase.auth().currentUser;

db.collectionGroup("posts").where("author", "==", user.uid).get()

セキュリティ ルールで、 postsコレクション グループの読み取りルールまたはリスト ルールを記述して、このクエリを許可する必要があります。

rules_version = '2';
service cloud.firestore {

  match /databases/{database}/documents {
    // Authenticated users can query the posts collection group
    // Applies to collection queries, collection group queries, and
    // single document retrievals
    match /{path=**}/posts/{post} {
      allow read: if request.auth != null;
    }
    match /forums/{forumid}/posts/{postid} {
      // Only a post's author can write to a post
      allow write: if request.auth != null && request.auth.uid == resource.data.author;

    }
  }
}

ただし、これらのルールは、階層に関係なく、ID postsを持つすべてのコレクションに適用されることに注意してください。たとえば、これらのルールは次のすべてのpostsコレクションに適用されます。

  • /posts/{postid}
  • /forums/{forumid}/posts/{postid}
  • /forums/{forumid}/subforum/{subforumid}/posts/{postid}

フィールドに基づくセキュアなコレクション グループ クエリ

単一コレクション クエリと同様に、コレクション グループ クエリも、セキュリティ ルールによって設定された制約を満たす必要があります。たとえば、上記のstories例で行ったように、各フォーラム投稿にpublishedフィールドを追加できます。

/forums/{forumid}/posts/{postid}

{
  author: "some_auth_id",
  authorname: "some_username",
  content: "I just read a great story.",
  published: false
}

次に、 publishedたステータスと投稿authorに基づいて、 postsコレクション グループのルールを記述できます。

rules_version = '2';
service cloud.firestore {

  match /databases/{database}/documents {

    // Returns `true` if the requested post is 'published'
    // or the user authored the post
    function authorOrPublished() {
      return resource.data.published == true || request.auth.uid == resource.data.author;
    }

    match /{path=**}/posts/{post} {

      // Anyone can query published posts
      // Authors can query their unpublished posts
      allow list: if authorOrPublished();

      // Anyone can retrieve a published post
      // Authors can retrieve an unpublished post
      allow get: if authorOrPublished();
    }

    match /forums/{forumid}/posts/{postid} {
      // Only a post's author can write to a post
      allow write: if request.auth.uid == resource.data.author;
    }
  }
}

これらのルールを使用すると、Web、Apple、および Android クライアントは次のクエリを実行できます。

  • 誰でもフォーラムで公開された投稿を取得できます。

    db.collection("forums/technology/posts").where('published', '==', true).get()
    
  • 誰でも、すべてのフォーラムで著者の公開された投稿を取得できます。

    db.collectionGroup("posts").where("author", "==", "some_auth_id").where('published', '==', true).get()
    
  • 作成者は、すべてのフォーラムで公開済みおよび未公開のすべての投稿を取得できます。

    var user = firebase.auth().currentUser;
    
    db.collectionGroup("posts").where("author", "==", user.uid).get()
    

コレクション グループとドキュメント パスに基づいてドキュメントを保護し、クエリを実行する

場合によっては、ドキュメント パスに基づいてコレクション グループのクエリを制限する必要があります。これらの制限を作成するには、フィールドに基づいてドキュメントを保護およびクエリするのと同じ手法を使用できます。

複数の証券取引所と暗号通貨取引所の間で各ユーザーの取引を追跡するアプリケーションを考えてみましょう。

/users/{userid}/exchange/{exchangeid}/transactions/{transaction}

{
  amount: 100,
  exchange: 'some_exchange_name',
  timestamp: April 1, 2019 at 12:00:00 PM UTC-7,
  user: "some_auth_id",
}

userフィールドに注意してください。ドキュメントのパスからどのユーザーがtransactionドキュメントを所有しているかがわかりますが、次の 2 つのことができるため、各transactionドキュメントでこの情報を複製します。

  • ドキュメント パスに特定の/users/{userid}を含むドキュメントに制限されたコレクション グループ クエリを記述します。例えば:

    var user = firebase.auth().currentUser;
    // Return current user's last five transactions across all exchanges
    db.collectionGroup("transactions").where("user", "==", user).orderBy('timestamp').limit(5)
    
  • あるユーザーが別のユーザーのtransactionドキュメントを取得できないように、 transactionsコレクション グループのすべてのクエリにこの制限を適用します。

セキュリティ ルールでこの制限を適用し、 userフィールドのデータ検証を含めます。

rules_version = '2';
service cloud.firestore {

  match /databases/{database}/documents {

    match /{path=**}/transactions/{transaction} {
      // Authenticated users can retrieve only their own transactions
      allow read: if resource.data.user == request.auth.uid;
    }

    match /users/{userid}/exchange/{exchangeid}/transactions/{transaction} {
      // Authenticated users can write to their own transactions subcollections
      // Writes must populate the user field with the correct auth id
      allow write: if userid == request.auth.uid && request.data.user == request.auth.uid
    }
  }
}

次のステップ