Firebaseデータベースでドキュメントごとにセキュリティルールを設定する

記事
IT・テクノロジー

Firebaseデータベースでドキュメントごとにセキュリティルールを設定する

Firebaseでデータベース(Cloud Firestore)を利用する場合、セキュリティルールの設定はとても大切です。 Firebaseコンソールでデータベース(Cloud Firestore)を作成した際に標準で設定されるルールはとてもシンプルなものです。実際の運用では、さらに細かいセキュリティルールが必要になる場合が殆どです。
コレクション毎のセキュリティルールの設定は比較的シンプルで分かりやすいので余り問題にならないと思いますが、ドキュメントごとにセキュリティルールを設定する場合は少し複雑になるのでなれないとルールの設定に時間を取られてしまう場合もあります。
この記事では、Firebaseの登録ユーザの拡張を考えた場合のセキュリティルールを例にセキュリティルールの設定の仕方を解説しています。

Firebaseが管理するユーザー情報

Firebaseでは、ユーザーの認証機能が用意されていてユーザーの管理や認証を簡単に実装することを可能にしています。
Firebaseから入手可能なユーザーの情報はE-Mailアドレスとパスワードの認証を理容師ている場合は次の様になります。
* E-Mailアドレス(email)
* ユーザーID(uid)
が基本的な情報です。これ以外の情報が必要な場合は、別にカスタムでテーブルを作って管理する必要があります。

グループごとにユーザーを管理することを考える

例えば、グループごとに管理者を置いてユーザーを管理することを考えます。 この場合は、Firebaseが提供している情報以外にどのグループに所属しているかという情報が必要になります。この情報をグループID(gid)という名前で設定する場合を考えてみます。
ユーザー管理の役割
ユーザー管理を行う上で考えられる役割として以下の3つの役割を考えます。
* システム管理者 ー 利用者全体の管理を行う
* グループ管理者 ー グループ内の利用者の管理を行う
* 一般ユーザー ー 自分自身の管理を行う
こうした役割を元にカスタムのユーザーのドキュメントの基本データを考えると
* emailもしくはuid
* ユーザの役割(role)
* グループID(gid)
* 名前(name)

というフィールドを持つデータとします。Firebaseのユーザー(firebase.User)に表示名(displayName)がありますが、ユーザー自身が変更できるフィールドとして、「名前(name)」を入れてあります。
これを、コレクション「users」に入れてユーザーを管理する場合を想定します。
ユーザーコレクション(users)
ユーザーコレクション(users)に入れるドキュメントのフィールドの定義です。

email ー変更不可
role ー システム管理者のみ変更可能
gid ー グループ管理者はグループ内のユーザの変更可能
name ー 利用者が変更可能
グループIDをどう決めるか?
グループIDの決め方はいろいろありますが、Firebaseを利用する場合、Firebaseが自動でドキュメントに割り当てるドキュメントIDを利用するのが簡単な方法です。
グループの情報を入れるコレクション(groups)を作って、グループを新規に作成する場合はこのコレクションにドキュメントを追加するようにすると、各グループに固有のIDを与えることができます。
グループコレクション(groups)
グループコレクション(groups)に入れるドキュメントのフィールドの定義です。
name ー グループ管理者が変更可能
システムの管理者の決め方
システムの管理者はFirebaseの特定のUIDのユーザーという形で決めてしまいます。 システムの管理者は、Firebaseのデータベース(Cloud Firestore)の全てのデータにアクセス可能とします。
セキュリティルールは以下の様になります。管理者のuidは「ABCDEFGHIJKLMNOPQRSTUVWXYZ」とします。このルールでこのuidを持つ全てのデータにアクセスできることになります。
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if request.auth.uid == "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    }
  }
}

必要なコレクションは?

この事例のユーザー管理に必要なコレクションは2つです。
* グループコレクション(groups)
* ユーザーコレクション(users)
です。
グループコレクション(groups)のアクセス権限
グループコレクションのアクセス権限を考えます。
* グループを新規に追加、削除できるのはシステム管理者のみ
* グループ名を変更できるのはグループ管理者
* 一般ユーザーはアクセス不可
(*)当然ですがシステム管理者は全ての権限を持つので特に明記していません。
ユーザーコレクション(users)のアクセス権限
ユーザーコレクションのアクセス権限は
* ユーザの作成はFirebaseで行う(Firebaseコンソールにアクセス権があるユーザーが行う)
* システム管理者は、全てのユーザーの役割・グループID・名前の変更が可能
* グループ管理者はグループ内のユーザの役割・名前の変更が可能
* 一般ユーザは自身の名前の変更が可能
読み込みに関しては
* システムの管理者は全て利用者のデータの読み込み
* グループ管理者はグループ内の全ての利用者の読み込み
* 一般ユーザーは自身のデータの読み込みのみ
以下のようなセキュリティルールで実現可能
* 各ユーザのドキュメントIDはuidと同じ
という前提でルールを設定
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // システム管理者は全ての権限
    match /{document=**} {
        allow read, write: if request.auth.uid == "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    }
    // アクセスしようとしているユーザーのドキュメントを取得
 function get_user_entry() {
     return get(/databases/$(database)/documents/users/$(request.auth.uid))
    }
    // グループコレクションのアクセス
    // グループ管理者かつ同じgidの場合読み書き可能
    match /groups/{groupId} {
        allow read, write: if get_user_entry().data.role == "admin" &&
            get_user_entry().data.gid == groupId
    }
    // ユーザーコレクションのアクセス
    match /users/{userId} {
        // 読み込みの許可
        // 本人のドキュメント
        // グループ管理者かつ同じGID
        allow read: if request.auth.uid == userId ||
        (get_user_entry().data.role == "admin" &&
             get_user_entry().data.gid == resource.data.gid);
        // 消去の許可
        // グループ管理者かつ同じGID
        allow delete: if (get_user_entry().data.role == "admin" &&
        get_user_entry().data.gid == resource.data.gid);
        // 更新の許可(グループ管理者)
        // gid と emailの変更は不可
        allow update: if get_user_entry().data.role == "admin" &&
            get_user_entry().data.gid == resource.data.gid &&
            request.resource.data.gid == resource.data.gid &&
            request.resource.data.email == resource.data.email;
        // 更新の許可(本人)
        // gid / email / roleの変更は不可
        allow update: if request.auth.uid == userId &&
        request.resource.data.gid == resource.data.gid &&
            request.resource.data.email == resource.data.email &&
            request.resource.data.role == resource.data.role;
    }
  }
}
以上のセキュリティルールを設定すると、許可のないドキュメントへのアクセスがフロントエンドからあった場合はエラーとなり、データの取得はできなくなります。
resource.data:アクセスしようとしているドキュメントのデータ
request.resource.data:更新データ(上書きしようとしているデータ)
を利用すると変更前と変更後のデータを比較してチェックする事ができます。

まとめ

データベースの構造を工夫すると、ドキュメントごとのセキュリティルールを設定しやすくなり場合があります。uidをドキュメント名やコレクション名に使うことで、セキュリティルールの中でのチェックが可能になるためです。
特定のドキュメントをFirebase Cloud Firestoreから取得してセキュリティルールの一部として利用することも可能です。この記事の例では、ユーザーコレクションからアクセスをしようとしているユーザーのドキュメントを取得して、グループIDや役割の情報を利用できるようになっています。
Firebaseのデータベース設計の際は、セキュリティルールをどのように設定するかを考慮したうえでデータベースの構造を考えることが重要です。
サービス数40万件のスキルマーケット、あなたにぴったりのサービスを探す