このドキュメントでは、データのセキュリティ保護のコンセプトを復習し、事前定義された auth
変数によって、データを安全に保護するためのソリューションを作成します。
認証の統合
Firebase Authentication は Firebase Realtime Database との統合により、ユーザーごとにデータアクセスを制御できます。
ユーザーが認証されると、Realtime Database Rules ルールの auth
変数にユーザーの情報が設定されます。この情報には、一意の識別子(uid
)に加え、Facebook の ID やメールアドレスなど、リンクされたアカウント データ、およびその他の情報が含まれます。カスタムの auth プロバイダを実装する場合は、独自のフィールドをユーザーの auth ペイロードに追加できます。
このガイドでは、Firebase Realtime Database ルール言語をユーザーの認証情報と組み合わせる方法について説明します。これらの 2 つの概念を組み合わせることにより、ユーザー ID に基づいたデータへのアクセス制御が可能になります。
auth
変数
ルール内で事前定義された auth
変数が null の場合に、認証は実行されます。
ユーザーが Firebase Authentication で認証されると、この変数には次の属性が含まれています。
provider | 使用された認証方法(「password」、「anonymous」、「facebook」、「github」、「google」、または「twitter」)。 |
uid | 一意のユーザー ID。すべてのプロバイダにわたって一意であることが保証されています。 |
token |
Firebase Auth ID トークンのコンテンツ。詳しくは、auth.token のリファレンス ドキュメントをご覧ください。
|
次に、auth
変数を使用して各ユーザーがユーザー固有のパスのみに書き込みできるようにするサンプルのルールを示します。
{ "rules": { "users": { "$user_id": { // grants write access to the owner of this user account // whose uid must exactly match the key ($user_id) ".write": "$user_id === auth.uid" } } } }
データベースの構造化
セキュリティ ルールの書き込みを容易にする方法でデータベースを構造化することが役に立つ場合があります。たとえば、ユーザーデータを Realtime Database に保存するための一般的なパターンの 1 つは、子がすべてのユーザーの uid
値である単一の users
ノードにすべてのユーザーを保存することです。このデータへのアクセスを制限してログイン ユーザーのみが自分のデータを参照できるようにしたい場合、ルールは次のようになります。
{ "rules": { "users": { "$uid": { ".read": "auth != null && auth.uid == $uid" } } } }
認証カスタム クレームの操作
さまざまなユーザーのカスタム アクセス制御が必要なアプリの場合、Firebase Authentication を使用すると、デベロッパーは Firebase ユーザーに対してクレームを設定できます。これらのクレームには、ルールの auth.token
変数でアクセスできます。hasEmergencyTowel
カスタム クレームを使用するルールの例を次に示します。
{ "rules": { "frood": { // A towel is about the most massively useful thing an interstellar // hitchhiker can have ".read": "auth.token.hasEmergencyTowel === true" } } }
独自のカスタム認証トークンを作成するデベロッパーは、オプションでこれらのトークンにクレームを追加できます。これらのクレームは、ルールの auth.token
変数で使用できます。
チャット サンプルの復習
データのセキュリティ保護のチャット サンプルに基づき、なんらかのユーザー認証を加え、これらの概念すべてを作業中のアプリに取り込みます。
{ "rules": { "room_names": { // any logged in user can get a list of room names ".read": "auth !== null", "$room_id": { // this is just for documenting the structure of rooms, since // they are read-only and no write rule allows this to be set ".validate": "newData.isString()" } }, "members": { // I can join or leave any room (otherwise it would be a boring demo) // I can have a different name in each room just for fun "$room_id": { // any member can read the list of member names ".read": "data.child(auth.uid).exists()", // room must already exist to add a member ".validate": "root.child('room_names/'+$room_id).exists()", "$user_id": { ".write": "auth.uid === $user_id", ".validate": "newData.isString() && newData.val().length > 0 && newData.val().length < 20" } } }, "messages": { "$room_id": { // the list of messages for a room can be read by any member ".read": "root.child('members/'+$room_id+'/'+auth.uid).exists()", // room we want to write a message to must be valid ".validate": "root.child('room_names/'+$room_id).exists()", "$message_id": { // a new message can be created if it does not exist, but it // cannot be modified or deleted // any member of a room can write a new message ".write": "root.child('members/'+$room_id+'/'+auth.uid).exists() && !data.exists() && newData.exists()", // the room attribute must be a valid key in room_names/ (the room must exist) // the object to write must have a name, message, and timestamp ".validate": "newData.hasChildren(['user', 'message', 'timestamp'])", // the message must be written by logged in user "user": { ".validate": "newData.val() === auth.uid" }, // the message must be longer than 0 chars and less than 50 "message": { ".validate": "newData.isString() && newData.val().length > 0 && newData.val().length < 50" }, // messages cannot be added in the past or the future // clients should use firebase.database.ServerValue.TIMESTAMP // to ensure accurate timestamps "timestamp": { ".validate": "newData.val() <= now" }, // no other fields can be included in a message "$other": { ".validate": false } } } } } }
次のステップ
- ルールを使用したインデックスの指定について詳しく学ぶ。