firebaseのAuthenticationとfirestoreを利用したセキュリティルール設定

ここではセキュリティルールを考えた際にfirestoreはどういったデータ構造にするか、ということを説明する。

詳細の設定については、 公式ブログ に譲る。

チャットアプリのような双方向の通信を実現する際は、 firebase を利用することで簡単に実装できる。

実際には、アプリケーション内で firestore のデータを監視して、データの追加/変更/削除が発生したら自身で用意したイベントを呼び出すことで、相手の 発信 を受け取ることができる。

ただ、 firestore は正しいセキュリティルールを設定しないと、 誰でも ストレージにアクセスができるので、必ずルールを設定しなければいけない。

ここで、 firestore を以下のように組み立てるとする(これは後に困るやり方)。

Collection Document Collection/data Document
chatRoom XXX member(C) YYY
message(C) ZZZ
name(d)
description(d)

アクセス制御を行うには、以下のような設定を行う。

match /chatRoom/{roomId} {
      allow read, write: if exists(/databases/$(database)/documents/chatRoom/$(roomId)/member/$(request.auth.uid));
}
// 上記以外全て拒否

このようにすることで、 /chatRoom にアクセスして全データを取得するときに、 /chatRoom/{roomId} の条件にヒットしないものは取得されない、と思ったが、このパス /chatRoom/{roomId} はリクエストパスを表現しているため、 このようにアクセス制御を行うと、 /chatRoom へのルールが設定されていないため、アクセス自体が拒否されてしまう。 /chatRoom の全取得で権限があるデータだけを取得する、という操作はできないことがわかる。

このことから、 firestore のデータ構造を以下のようにする必要がある。user コレクションには、 chatRoom の情報を非正規化して保持している。ただ、非正規化が行われるということは、チャットルーム名の変更などが発生した場合は、非正規化が発生した部分を全て変更しなければならない。firebaseでは、 collectionGroup() というメソッドでサブコレクションを串刺しにして取得することができるので、collectionGroup(YYY) とすることで、非正規化されたデータを一括で書き換えることができるようになる。

(※ user のサブコレクションの userRoomroom にしなかった理由はサブコレクションを串刺しにしたいときに、 room は他のサブコレクション名とバッティングする恐れがあるため、 userRoom とした)

Collection Document Collection/data Document data
chatRoom XXX message(C) ZZZ
name(d)
description(d)
user XXX userRoom(C) YYY name(d)
description(d)

このようにすることで、チャット一覧の取得は、 /user/{user}/userRoom で取得するようになる。このとき、認証されたユーザしか /user/{user}/userRoom のデータにアクセスできない。チャットの詳細を取得するときは、 /chatRoom/{roomId} にアクセスして、認証されているユーザがこのルームへのアクセス権限があるかを判定している。

match /user/{user}/userRoom {
      allow read, write: if user == request.auth.uid;
}
match /chatRoom/{roomId} {
      allow read, write: if exists(/databases/$(database)/documents/user/$(request.auth.uid)/userRoom/$(roomId));
}
// 上記以外全て拒否

これでセキュアな状態(非正規化ではあるもののデータ更新に困らない)のモノになるのではないだろうか。