ユーザーベースのセキュリティ

このドキュメントでは、データのセキュリティ保護の概念を復習し、事前定義された auth 変数によって、データを安全に保護するためのソリューションを作成します。

認証の統合

Firebase Authentication は Firebase Realtime Database との統合により、ユーザーごとにデータアクセスを制御できます。

ユーザーが認証されると、Firebase Database ルールのルール内の 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 を使用して独自のカスタム認証トークンをサーバーに作成できます。

独自のカスタム認証トークンを作成するデベロッパーは、オプションでこれらのトークンにクレームを追加できます。これらのクレームは、ルールの auth.token 変数に表示されます。hasEmergencyTowel カスタム クレームを使用するルールの例を次に示します。

{
  "rules": {
    "frood": {
      // A towel is about the most massively useful thing an interstellar
      // hitchhiker can have
      ".read": "auth.token.hasEmergencyTowel === true"
    }
  }
}

チャットサンプルの復習

データのセキュリティ保護のチャットサンプルに基づき、なんらかのユーザー認証を加え、これらの概念すべてを作業中のアプリに取り込みます。

{
  "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 }
        }
      }
    }
  }
}

次のステップ

フィードバックを送信...

ご不明な点がありましたら、Google のサポートページをご覧ください。