Firebaseセキュリーティールールでバリデーション


この記事は、Firebase #2 Advent Calendar 2019の5日目の記事です。

Firestoreでのバリデーション

Firebaseが便利です。Authenticationを使って簡単に認証出来て、認証が無ければ、FirestoreやFirestorageからデータが取れないようにバリデーションをかける事も簡単です。
しかし、それ以上のバリデーションも勿論かけておかないとMySQLやPosgresSQLでカラム毎に型やサイズをサーバー側で決めている事に比べセキュリティが甘くなりそうな感じがします。フロントエンド側でのバリデーションに加えてFirebase側でもバリデーションをかけれるように出来る事を整理してみました。

CloudFirestoreセキュリティルールというのがある

使った事がある方誰もが知っているFirebaseコンソールでデータベースの作成を押した時に本番環境で開始、テストモードで開始を選ぶ時に触る所です。

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /<some_path>/ {
      allow read, write: if <ここに条件を書く>;
    }
  }
}

まずrules_version = '2';ですが、2019年5月にCloudFirestoreのセキュリティルールのバージョン2が使用可能になりました。バージョン2では再帰的なワイルドカードの動作が変更されています。

<some_path>の部分にはルールを適用したいpathを記述できます。例えば、ユーザー固有の情報を格納しているアドレスとかを書くことが出来ます。

allow read, write: if <ここに条件を書く>

allow read, writeの部分にどのような動作に対して
の部分に様々な条件を書くことが出来ます。

認証を必要とする時、

allow read, write: if request.auth.uid != null;

全て拒否になる条件

allow read, write: if false;

全て許可になる条件

allow read, write: if true;

read、writeはさらに細かく設定できる

readは、getとlistに分けることが出来ます。
get: 一つのドキュメントの取得
list: クエリとコレクションの取得

writeは、 create, update, deleteに分けることができる。

create: 新規のドキュメントの書き込み
update: ドキュメントの上書き
delete: ドキュメントの削除

service cloud.firestore {
  match /databases/{database}/documents {
    // 
    match /users/{userId} {
      // 一つのドキュメントの取得
      allow get: if <condition>;

      // クエリとコレクションを指定して一覧を取得
      allow list: if <condition>;
    }

    // A write rule can be divided into create, update, and delete rules
    match /users/{userId} {
      // 新規のドキュメントの書き込み
      allow create: if <condition>;

      // ドキュメントの上書き
      allow update: if <condition>;

      // ドキュメントの削除
      allow delete: if <condition>;
    }
  }
}

条件について

MySQL等でのデータベースでならやれる事はFireStoreでもやりたい。

  • ログインしている人だけが使えるようにバリデーションしたい。

  • ログインしている人だけが自身のユーザー情報に紐づいた情報にアクセスできる。(他の人のユーザー情報に紐づいた情報にはアクセス出来ない。

  • 送られてくるデータのスキーマが正しいかバリデーションにかけたい。

  • twitterのように投稿文字数でバリデーションかけたい。

今回はこの4つを実現していきます。

認証があるかないかで分ける。

request.auth.uidで認証情報を取得出来ます。

allow read, write: if request.auth.uid != null

自分の情報にしかアクセス出来ないようにする

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /petowners/{ownerId} {
      request.auth.uid == resource.data.userId;
    }
  }
}

resource.dataで、現在のドキュメントに格納されているフィールドと値のmapを参照できます。

送られてくるデータのスキーマが正しいかバリデーションにかけたい。

request.resource.data.<キー> を使用して、ドキュメントの書き込みのために送信された値が特定の書式を満たしていることを確認出来ます。

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

twitterのように投稿文字数でバリデーションかけたい。

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;

関数を使って条件を定義する

さらにセキュリティールールが複雑になって切り出したい。
複数回呼び出したいという時に関数に定義できます。

service cloud.firestore {
  match /databases/{database}/documents {
    // True if the user is signed in or the requested data is 'public'
    function signedInOrPublic() {
      return request.auth.uid != null || resource.data.visibility == 'public';
    }

    match /cities/{city} {
      allow read, write: if signedInOrPublic();
    }

    match /users/{user} {
      allow read, write: if signedInOrPublic();
    }
  }
}

まとめ

全てのセキュリティルールを理解できていませんが、実際にサンプルアプリでも何か作る時にFirebase側でもセキュリティを意識して、使っていきたいと思います。個人でちょっと使ってみる時にも、Firebaseで提供されているセキュリティルールを使って可能な限りセキュリティの高い設定を出来るように、心がけてFirebaseが得意になっていきたいです。