2022 年 10 月 18 日に開催される Firebase Summit に、直接会場で、またはオンラインでご参加ください。Firebase を使用してアプリ開発を加速させ、自信を持ってアプリをリリースし、簡単にスケールする方法をご紹介します。 今すぐ申し込む

リアルタイムデータベースセキュリティルールの使用条件

このガイドは、Firebase リアルタイム データベース セキュリティ ルールに条件を追加する方法を示す、コア Firebase セキュリティ ルール言語ガイドの学習に基づいています。

Realtime Database セキュリティ ルールの主な構成要素は条件です。条件は、特定の操作を許可するか拒否するかを決定するブール式です。基本的なルールでは、 truefalseリテラルを条件として使用すると、うまく機能します。ただし、Realtime Database Security Rules 言語を使用すると、次のことができるより複雑な条件を記述できます。

  • ユーザー認証を確認する
  • 新しく提出されたデータに対して既存のデータを評価する
  • データベースのさまざまな部分にアクセスして比較する
  • 受信データを検証する
  • セキュリティ ロジックに着信クエリの構造を使用する

$ 変数を使用してパス セグメントをキャプチャする

$プレフィックスを付けてキャプチャ変数を宣言することにより、読み取りまたは書き込みのパスの一部をキャプチャできます。これはワイルドカードとして機能し、ルール条件内で使用するためにそのキーの値を保存します。

{
  "rules": {
    "rooms": {
      // this rule applies to any child of /rooms/, the key for each room id
      // is stored inside $room_id variable for reference
      "$room_id": {
        "topic": {
          // the room's topic can be changed if the room id has "public" in it
          ".write": "$room_id.contains('public')"
        }
      }
    }
  }
}

動的$変数は、定数パス名と並行して使用することもできます。この例では、 $other変数を使用して、 widgettitlecolor以外の子がないことを保証する.validateルールを宣言しています。追加の子が作成される書き込みは失敗します。

{
  "rules": {
    "widget": {
      // a widget can have a title or color attribute
      "title": { ".validate": true },
      "color": { ".validate": true },

      // but no other child paths are allowed
      // in this case, $other means any key excluding "title" and "color"
      "$other": { ".validate": false }
    }
  }
}

認証

最も一般的なセキュリティ ルール パターンの 1 つは、ユーザーの認証状態に基づいてアクセスを制御することです。たとえば、サインインしているユーザーだけにデータの書き込みを許可することがアプリで必要になる場合があります。

アプリが Firebase Authentication を使用している場合、 request.auth変数には、データを要求するクライアントの認証情報が含まれます。 request.authの詳細については、リファレンス ドキュメントを参照してください。

Firebase Authentication は Firebase Realtime Database と統合されているため、条件を使用してユーザーごとにデータ アクセスを制御できます。ユーザーが認証されると、Realtime Database Security Rules ルールのauth変数にユーザーの情報が入力されます。この情報には、一意の識別子 ( uid ) のほか、Facebook ID や電子メール アドレスなどのリンクされたアカウント データ、およびその他の情報が含まれます。カスタム認証プロバイダーを実装する場合、独自のフィールドをユーザーの認証ペイロードに追加できます。

このセクションでは、Firebase Realtime Database セキュリティ ルール言語をユーザーの認証情報と組み合わせる方法について説明します。これら 2 つの概念を組み合わせることで、ユーザー ID に基づいてデータへのアクセスを制御できます。

auth変数

認証が行われる前は、ルール内の事前定義されたauth変数は null です。

ユーザーがFirebase Authenticationで認証されると、次の属性が含まれます。

プロバイダー使用される認証方法 (「password」、「anonymous」、「facebook」、「github」、「google」、または「twitter」)。
イドすべてのプロバイダーで一意であることが保証されている一意のユーザー ID。
トークン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変数で利用できます。

既存のデータと新しいデータ

事前定義されたdata変数は、書き込み操作が行われる前にデータを参照するために使用されます。逆に、 newData変数には、書き込み操作が成功した場合に存在する新しいデータが含まれます。 newDataは、書き込まれている新しいデータと既存のデータのマージ結果を表します。

たとえば、このルールでは、新しいレコードを作成したり、既存のレコードを削除したりできますが、既存の null 以外のデータを変更することはできません。

// we can write as long as old data or new data does not exist
// in other words, if this is a delete or a create, but not an update
".write": "!data.exists() || !newData.exists()"

他のパスのデータを参照する

ルールの基準として任意のデータを使用できます。事前定義された変数rootdata 、およびnewDataを使用して、書き込みイベントの前後に存在する任意のパスにアクセスできます。

/allow_writes/ノードの値がtrueで、親ノードにreadOnlyフラグが設定されておらず、新しく書き込まれたデータにfooという名前の子がある限り、書き込み操作を許可する次の例を検討してください。

".write": "root.child('allow_writes').val() === true &&
          !data.parent().child('readOnly').exists() &&
          newData.child('foo').exists()"

データの検証

データ構造の強制と、データの形式と内容の検証は、 .validateルールを使用して行う必要があります。このルールは、 .writeルールがアクセスの許可に成功した後にのみ実行されます。以下は、正規表現を使用してチェックされる 1900 年から 2099 年の間の YYYY-MM-DD 形式の日付のみを許可する.validateルール定義のサンプルです。

".validate": "newData.isString() &&
              newData.val().matches(/^(19|20)[0-9][0-9][-\\/. ](0[1-9]|1[012])[-\\/. ](0[1-9]|[12][0-9]|3[01])$/)"

.validateルールは、カスケードしない唯一のタイプのセキュリティ ルールです。いずれかの子レコードで検証ルールが失敗した場合、書き込み操作全体が拒否されます。さらに、データが削除されるとき (つまり、書き込まれる新しい値がnullのとき)、検証定義は無視されます。

これらは些細なことのように思えるかもしれませんが、実際には強力な Firebase Realtime Database セキュリティ ルールを作成するための重要な機能です。次のルールを考慮してください。

{
  "rules": {
    // write is allowed for all paths
    ".write": true,
    "widget": {
      // a valid widget must have attributes "color" and "size"
      // allows deleting widgets (since .validate is not applied to delete rules)
      ".validate": "newData.hasChildren(['color', 'size'])",
      "size": {
        // the value of "size" must be a number between 0 and 99
        ".validate": "newData.isNumber() &&
                      newData.val() >= 0 &&
                      newData.val() <= 99"
      },
      "color": {
        // the value of "color" must exist as a key in our mythical
        // /valid_colors/ index
        ".validate": "root.child('valid_colors/' + newData.val()).exists()"
      }
    }
  }
}

このバリアントを念頭に置いて、次の書き込み操作の結果を見てください。

JavaScript
var ref = db.ref("/widget");

// PERMISSION_DENIED: does not have children color and size
ref.set('foo');

// PERMISSION DENIED: does not have child color
ref.set({size: 22});

// PERMISSION_DENIED: size is not a number
ref.set({ size: 'foo', color: 'red' });

// SUCCESS (assuming 'blue' appears in our colors list)
ref.set({ size: 21, color: 'blue'});

// If the record already exists and has a color, this will
// succeed, otherwise it will fail since newData.hasChildren(['color', 'size'])
// will fail to validate
ref.child('size').set(99);
Objective-C
注:この Firebase 製品は、App Clip ターゲットでは使用できません。
FIRDatabaseReference *ref = [[[FIRDatabase database] reference] child: @"widget"];

// PERMISSION_DENIED: does not have children color and size
[ref setValue: @"foo"];

// PERMISSION DENIED: does not have child color
[ref setValue: @{ @"size": @"foo" }];

// PERMISSION_DENIED: size is not a number
[ref setValue: @{ @"size": @"foo", @"color": @"red" }];

// SUCCESS (assuming 'blue' appears in our colors list)
[ref setValue: @{ @"size": @21, @"color": @"blue" }];

// If the record already exists and has a color, this will
// succeed, otherwise it will fail since newData.hasChildren(['color', 'size'])
// will fail to validate
[[ref child:@"size"] setValue: @99];
迅速
注:この Firebase 製品は、App Clip ターゲットでは使用できません。
var ref = FIRDatabase.database().reference().child("widget")

// PERMISSION_DENIED: does not have children color and size
ref.setValue("foo")

// PERMISSION DENIED: does not have child color
ref.setValue(["size": "foo"])

// PERMISSION_DENIED: size is not a number
ref.setValue(["size": "foo", "color": "red"])

// SUCCESS (assuming 'blue' appears in our colors list)
ref.setValue(["size": 21, "color": "blue"])

// If the record already exists and has a color, this will
// succeed, otherwise it will fail since newData.hasChildren(['color', 'size'])
// will fail to validate
ref.child("size").setValue(99);
ジャワ
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference ref = database.getReference("widget");

// PERMISSION_DENIED: does not have children color and size
ref.setValue("foo");

// PERMISSION DENIED: does not have child color
ref.child("size").setValue(22);

// PERMISSION_DENIED: size is not a number
Map<String,Object> map = new HashMap<String, Object>();
map.put("size","foo");
map.put("color","red");
ref.setValue(map);

// SUCCESS (assuming 'blue' appears in our colors list)
map = new HashMap<String, Object>();
map.put("size", 21);
map.put("color","blue");
ref.setValue(map);

// If the record already exists and has a color, this will
// succeed, otherwise it will fail since newData.hasChildren(['color', 'size'])
// will fail to validate
ref.child("size").setValue(99);
休み
# PERMISSION_DENIED: does not have children color and size
curl -X PUT -d 'foo' \
https://docs-examples.firebaseio.com/rest/securing-data/example.json

# PERMISSION DENIED: does not have child color
curl -X PUT -d '{"size": 22}' \
https://docs-examples.firebaseio.com/rest/securing-data/example.json

# PERMISSION_DENIED: size is not a number
curl -X PUT -d '{"size": "foo", "color": "red"}' \
https://docs-examples.firebaseio.com/rest/securing-data/example.json

# SUCCESS (assuming 'blue' appears in our colors list)
curl -X PUT -d '{"size": 21, "color": "blue"}' \
https://docs-examples.firebaseio.com/rest/securing-data/example.json

# If the record already exists and has a color, this will
# succeed, otherwise it will fail since newData.hasChildren(['color', 'size'])
# will fail to validate
curl -X PUT -d '99' \
https://docs-examples.firebaseio.com/rest/securing-data/example/size.json

同じ構造を見てみましょうが、 .validateの代わりに.writeルールを使用しています:

{
  "rules": {
    // this variant will NOT allow deleting records (since .write would be disallowed)
    "widget": {
      // a widget must have 'color' and 'size' in order to be written to this path
      ".write": "newData.hasChildren(['color', 'size'])",
      "size": {
        // the value of "size" must be a number between 0 and 99, ONLY IF WE WRITE DIRECTLY TO SIZE
        ".write": "newData.isNumber() && newData.val() >= 0 && newData.val() <= 99"
      },
      "color": {
        // the value of "color" must exist as a key in our mythical valid_colors/ index
        // BUT ONLY IF WE WRITE DIRECTLY TO COLOR
        ".write": "root.child('valid_colors/'+newData.val()).exists()"
      }
    }
  }
}

この亜種では、次の操作のいずれかが成功します。

JavaScript
var ref = new Firebase(URL + "/widget");

// ALLOWED? Even though size is invalid, widget has children color and size,
// so write is allowed and the .write rule under color is ignored
ref.set({size: 99999, color: 'red'});

// ALLOWED? Works even if widget does not exist, allowing us to create a widget
// which is invalid and does not have a valid color.
// (allowed by the write rule under "color")
ref.child('size').set(99);
Objective-C
注:この Firebase 製品は、App Clip ターゲットでは使用できません。
Firebase *ref = [[Firebase alloc] initWithUrl:URL];

// ALLOWED? Even though size is invalid, widget has children color and size,
// so write is allowed and the .write rule under color is ignored
[ref setValue: @{ @"size": @9999, @"color": @"red" }];

// ALLOWED? Works even if widget does not exist, allowing us to create a widget
// which is invalid and does not have a valid color.
// (allowed by the write rule under "color")
[[ref childByAppendingPath:@"size"] setValue: @99];
迅速
注:この Firebase 製品は、App Clip ターゲットでは使用できません。
var ref = Firebase(url:URL)

// ALLOWED? Even though size is invalid, widget has children color and size,
// so write is allowed and the .write rule under color is ignored
ref.setValue(["size": 9999, "color": "red"])

// ALLOWED? Works even if widget does not exist, allowing us to create a widget
// which is invalid and does not have a valid color.
// (allowed by the write rule under "color")
ref.childByAppendingPath("size").setValue(99)
ジャワ
Firebase ref = new Firebase(URL + "/widget");

// ALLOWED? Even though size is invalid, widget has children color and size,
// so write is allowed and the .write rule under color is ignored
Map<String,Object> map = new HashMap<String, Object>();
map.put("size", 99999);
map.put("color", "red");
ref.setValue(map);

// ALLOWED? Works even if widget does not exist, allowing us to create a widget
// which is invalid and does not have a valid color.
// (allowed by the write rule under "color")
ref.child("size").setValue(99);
休み
# ALLOWED? Even though size is invalid, widget has children color and size,
# so write is allowed and the .write rule under color is ignored
curl -X PUT -d '{size: 99999, color: "red"}' \
https://docs-examples.firebaseio.com/rest/securing-data/example.json

# ALLOWED? Works even if widget does not exist, allowing us to create a widget
# which is invalid and does not have a valid color.
# (allowed by the write rule under "color")
curl -X PUT -d '99' \
https://docs-examples.firebaseio.com/rest/securing-data/example/size.json

これは、 .validate .writeの違いを示しています。示されているように、削除を許可するかどうかに依存するnewData.hasChildren()ルールを除いて、これらのルールはすべて.validateを使用して記述する必要があります。

クエリベースのルール

ルールをフィルタとして使用することはできませんが、ルールでクエリ パラメータを使用することにより、データのサブセットへのアクセスを制限できます。 query.式をルールに追加して、クエリ パラメータに基づいて読み取りまたは書き込みアクセスを許可します。

たとえば、次のクエリ ベースのルールは、ユーザー ベースのセキュリティ ルールとクエリ ベースのルールを使用して、 basketsコレクション内のデータへのアクセスを、アクティブなユーザーが所有するショッピング バスケットのみに制限します。

"baskets": {
  ".read": "auth.uid !== null &&
            query.orderByChild === 'owner' &&
            query.equalTo === auth.uid" // restrict basket access to owner of basket
}

ルールにクエリ パラメータを含む次のクエリは成功します。

db.ref("baskets").orderByChild("owner")
                 .equalTo(auth.currentUser.uid)
                 .on("value", cb)                 // Would succeed

ただし、ルールにパラメーターが含まれていないクエリは、 PermissionDeniedエラーで失敗します。

db.ref("baskets").on("value", cb)                 // Would fail with PermissionDenied

クエリベースのルールを使用して、読み取り操作によってクライアントがダウンロードするデータの量を制限することもできます。

たとえば、次のルールは、優先順位に従って、クエリの最初の 1000 件の結果のみに読み取りアクセスを制限します。

messages: {
  ".read": "query.orderByKey &&
            query.limitToFirst <= 1000"
}

// Example queries:

db.ref("messages").on("value", cb)                // Would fail with PermissionDenied

db.ref("messages").limitToFirst(1000)
                  .on("value", cb)                // Would succeed (default order by key)

次のquery.式は Realtime Database セキュリティ ルールで使用できます。

クエリベースのルール式
表現タイプ説明
query.orderByKey
query.orderByPriority
query.orderByValue
ブール値キー、優先度、または値で並べ替えられたクエリの場合は true。それ以外の場合は偽。
query.orderByChildストリング
ヌル
文字列を使用して、子ノードへの相対パスを表します。たとえば、 query.orderByChild === "address/zip"です。クエリが子ノードによって順序付けられていない場合、この値は null です。
query.startAt
query.endAt
query.equalTo
ストリング
番号
ブール値
ヌル
実行中のクエリの境界を取得するか、境界セットがない場合は null を返します。
query.limitToFirst
query.limitToLast
番号
ヌル
実行中のクエリの制限を取得するか、制限が設定されていない場合は null を返します。

次のステップ

この条件の説明の後、ルールについてより高度な理解が得られ、次のことができるようになります。

コア ユース ケースの処理方法を学び、ルールの開発、テスト、デプロイのワークフローを学びます。

Realtime Database に固有のルール機能について学習します。