セキュリティ ルールの仕組み

セキュリティは、アプリ開発というパズルにおいて最も複雑なピースのうちの 1 つです。ほとんどのアプリケーションでは、認証(ユーザーは誰なのか)と承認(ユーザーは何を実行できるのか)を行うサーバーをデベロッパーが構築して実行する必要があります。

Firebase セキュリティ ルールでは中間(サーバー)層を取り除き、データに直接アクセスするクライアントに対し、ユーザーがパスベースの権限を指定できます。このガイドを使用して、受信リクエストにルールが適用される仕組みの詳細について確認してください。

ルールの詳細を確認するプロダクトを選択してください。

Cloud Firestore

基本構造

Cloud Firestore と Cloud Storage の Firebase セキュリティ ルールでは、次の構造と構文を使用します。

service <<name>> {
  // Match the resource path.
  match <<path>> {
    // Allow the request if the following conditions are true.
    allow <<methods>> : if <<condition>>
  }
}

ルールを作成する際は、次の主要なコンセプトの理解が重要です。

  • リクエスト: allow ステートメント内で呼び出される 1 つ以上のメソッド。これらのメソッドに対して実行を許可します。標準メソッドは getlistcreateupdatedelete です。readwrite は、指定したデータベースまたはストレージのパスへの広範な読み取りや書き込みアクセスを可能にするコンビニエンス メソッドです。
  • パス: データベースまたはストレージの場所。URI パスとして表されます。
  • ルール: allow ステートメント。このステートメントに組み込まれた条件で true と評価されると、リクエストが許可されます。

セキュリティ ルール バージョン 2

2019 年 5 月以降、Firebase セキュリティ ルールのバージョン 2 が使用可能になりました。バージョン 2 のルールは、再帰的なワイルドカード {name=**} の動作を変更します。コレクション グループ クエリを使用する場合は、バージョン 2 を使用する必要があります。セキュリティ ルールで rules_version = '2'; を最初の行にして、バージョン 2 にオプトインする必要があります。

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {

パスの照合

すべての match ステートメントは、コレクションではなくドキュメントを指す必要があります。match ステートメントでは match /cities/SF のように特定のドキュメントを指すことや、match /cities/{city} のようにワイルドカードを使用して、指定されたパスのすべてのドキュメントを指すことができます。

上の例では、match ステートメントで {city} ワイルドカード構文を使用しています。つまり、このルールは、cities コレクションの、/cities/SF/cities/NYC などのすべてのドキュメントに適用されます。match ステートメントの allow 式が評価されると、city 変数は SFNYC などの city のドキュメント名に解決されます。

サブコレクションの照合

Cloud Firestore のデータはドキュメントのコレクションに編成され、各ドキュメントはサブコレクションによって階層を拡張できます。セキュリティ ルールと階層データのインタラクションの仕組みを理解することが重要です。

cities コレクションの各ドキュメントに landmarks サブコレクションが含まれている状況を考えてみましょう。セキュリティ ルールは一致したパスにのみ適用されるため、cities コレクションで定義されたアクセス制御は landmarks サブコレクションには適用されません。代わりに、サブコレクションへのアクセスを制御する明示的なルールを記述します。

service cloud.firestore {
  match /databases/{database}/documents {
    match /cities/{city} {
      allow read, write: if <condition>;

      // Explicitly define rules for the 'landmarks' subcollection
      match /landmarks/{landmark} {
        allow read, write: if <condition>;
      }
    }
  }
}

match ステートメントをネストする場合、内部の match ステートメントのパスは、常に、外側の match ステートメントのパスに対する相対パスになります。したがって、次のルールセットは同等です。

service cloud.firestore {
  match /databases/{database}/documents {
    match /cities/{city} {
      match /landmarks/{landmark} {
        allow read, write: if <condition>;
      }
    }
  }
}
service cloud.firestore {
  match /databases/{database}/documents {
    match /cities/{city}/landmarks/{landmark} {
      allow read, write: if <condition>;
    }
  }
}

再帰ワイルドカード

ルールが任意の深い階層に適用されるようにするには、再帰ワイルドカード構文、{name=**} を使用します。

service cloud.firestore {
  match /databases/{database}/documents {
    // Matches any document in the cities collection as well as any document
    // in a subcollection.
    match /cities/{document=**} {
      allow read, write: if <condition>;
    }
  }
}

再帰ワイルドカード構文を使用する場合、ネストが深いサブコレクションにドキュメントが置かれている場合も、ワイルドカード変数には一致するパスセグメント全体が含められます。たとえば、上に示したルールは、/cities/SF/landmarks/coit_tower に置かれたドキュメントと一致し、document 変数の値は SF/landmarks/coit_tower になります。

ただし、再帰ワイルドカードの動作は、ルールのバージョンによって異なります。

バージョン 1

セキュリティ ルールは、デフォルトでバージョン 1 を使用します。バージョン 1 では、再帰ワイルドカードは、1 つ以上のパスアイテムと一致します。再帰ワイルドカードは空のパスには一致しません。したがって、match /cities/{city}/{document=**} はサブコレクションのドキュメントと一致しますが、cities コレクションのものには一致しません。一方、match /cities/{document=**}cities コレクションとサブコレクションのドキュメントの両方に一致します。

再帰ワイルドカードは、match ステートメントの最後に指定する必要があります。

バージョン 2

バージョン 2 のセキュリティ ルールでは、再帰ワイルドカードは 0 個以上のパスアイテムに一致します。match/cities/{city}/{document=**} は、任意のサブコレクション内のドキュメントと cities コレクション内のドキュメントに一致します。

セキュリティ ルールの先頭に rules_version = '2'; を追加して、バージョン 2 にオプトインする必要があります。

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // Matches any document in the cities collection as well as any document
    // in a subcollection.
    match /cities/{city}/{document=**} {
      allow read, write: if <condition>;
    }
  }
}

match ステートメントごとに使用できる再帰ワイルドカードは 1 つだけですが、バージョン 2 では、このワイルドカードを match ステートメント内の任意の場所に配置できます。次に例を示します。

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // Matches any document in the songs collection group
    match /{path=**}/songs/{song} {
      allow read, write: if <condition>;
    }
  }
}

コレクション グループ クエリを使用する場合は、バージョン 2 を使用する必要があります。コレクション グループ クエリの保護をご覧ください。

match ステートメントの重複

1 つのドキュメントが複数の match ステートメントと一致する可能性があります。複数の allow 式がリクエストと一致する場合、いずれかの条件が true と評価されると、アクセスが許可されます。

service cloud.firestore {
  match /databases/{database}/documents {
    // Matches any document in the 'cities' collection.
    match /cities/{city} {
      allow read, write: if false;
    }

    // Matches any document in the 'cities' collection or subcollections.
    match /cities/{document=**} {
      allow read, write: if true;
    }
  }
}

上の例では、cities コレクションに対するすべての読み取りと書き込みが許可されます。これは、最初のルールが常に false になっても、2 番目のルールが常に true になるためです。

セキュリティ ルールの上限

セキュリティ ルールに関する操作では、次の制限に注意してください。

制限 詳細
リクエストあたりの exists()get()getAfter() 呼び出しの最大数
  • 単一ドキュメントに対するリクエストとクエリ リクエストの場合は 10。
  • 複数のドキュメントに対する読み取り、トランザクション、一括書き込みの場合は 20。各オペレーションには、前述の上限(10)も適用されます。

    たとえば、3 つの書き込みオペレーションを含めた一括書き込みリクエストを作成するとします。セキュリティ ルールでは、ドキュメントに対する 2 件のアクセス呼び出しを使用して、それぞれの書き込みを検証します。この場合、各書き込みオペレーションがアクセス呼び出し制限数 10 のうちの 2 つを使用するため、バッチ書き込みリクエストはアクセス呼び出し制限数 20 のうちの 6 つを使用することになります。

いずれかの上限を超えると、アクセス拒否のエラーが発生します。

一部のドキュメントに対するアクセス呼び出しはキャッシュされる場合があります。キャッシュされた呼び出しは上限数に計上されません。

ネストされた match ステートメントの深さの最大数 10
ネストされた一連の match ステートメント内で許可されるパスセグメント内の最大パス長 100
ネストされた一連の match ステートメント内で許可されるパスキャプチャ変数の最大数 20
関数呼び出しの深さの最大数 20
関数引数の最大数 7
関数あたりの let 変数バインディングの最大数 10
関数の再帰的な呼び出し、または循環的な呼び出しの最大数 0(許可されていません)
リクエストあたり評価される式の最大数 1,000
ルールセットの最大サイズ ルールセットは、次の 2 つのサイズ上限に従う必要があります。
  • Firebase コンソールまたは CLI から firebase deploy を使用して公開されるルールセット テキストソースのサイズは 256 KB に制限されています。
  • Firebase がソースを処理してバックエンドでアクティブにするときに生成される、コンパイル済みルールセットのサイズは 250 KB に制限されています。

Cloud Storage

基本構造

Cloud Firestore と Cloud Storage の Firebase セキュリティ ルールでは、次の構造と構文を使用します。

service <<name>> {
  // Match the resource path.
  match <<path>> {
    // Allow the request if the following conditions are true.
    allow <<methods>> : if <<condition>>
  }
}

ルールを作成する際は、次の主要なコンセプトの理解が重要です。

  • リクエスト: allow ステートメント内で呼び出される 1 つ以上のメソッド。これらのメソッドに対して実行を許可します。標準メソッドは getlistcreateupdatedelete です。readwrite は、指定したデータベースまたはストレージのパスへの広範な読み取りや書き込みアクセスを可能にするコンビニエンス メソッドです。
  • パス: データベースまたはストレージの場所。URI パスとして表されます。
  • ルール: allow ステートメント。このステートメントに組み込まれた条件で true と評価されると、リクエストが許可されます。

パスの照合

Cloud Storage セキュリティ ルールでは、match を使用して、Cloud Storage 内のファイルにアクセスするためのファイルパスを照合します。また、match を使用して完全なパスまたはワイルドカード パスと照合することができ、ルールをネストすることもできます。リクエスト メソッドを許可する照合ルールがない場合や、条件が false になる場合、リクエストは拒否されます。

完全一致

// Exact match for "images/profilePhoto.png"
match /images/profilePhoto.png {
  allow write: if <condition>;
}

// Exact match for "images/croppedProfilePhoto.png"
match /images/croppedProfilePhoto.png {
  allow write: if <other_condition>;
}

ネストされた一致

// Partial match for files that start with "images"
match /images {
  // Exact match for "images/profilePhoto.png"
  match /profilePhoto.png {
    allow write: if <condition>;
  }

  // Exact match for "images/croppedProfilePhoto.png"
  match /croppedProfilePhoto.png {
    allow write: if <other_condition>;
  }
}

ワイルドカードの照合

ルールでは、match を使用して、ワイルドカードを使ったパターンと照合させることもできます。ワイルドカードは名前付き変数で、1 つの文字列(profilePhoto.png など)や複数のパスセグメント(images/profilePhoto.png など)を表します。

ワイルドカードは、{string} のように、ワイルドカード名を中かっこで囲んで作成します。複数のセグメントのワイルドカードを宣言するには、{path=**} のように =** をワイルドカード名に追加します。

// Partial match for files that start with "images"
match /images {
  // Exact match for "images/*"
  // e.g. images/profilePhoto.png is matched
  match /{imageId} {
    // This rule only matches a single path segment (*)
    // imageId is a string that contains the specific segment matched
    allow read: if <condition>;
  }

  // Exact match for "images/**"
  // e.g. images/users/user:12345/profilePhoto.png is matched
  // images/profilePhoto.png is also matched!
  match /{allImages=**} {
    // This rule matches one or more path segments (**)
    // allImages is a path that contains all segments matched
    allow read: if <other_condition>;
  }
}

複数のルールが 1 つのファイルと一致する場合、結果は、すべてのルール評価の結果の OR になります。つまり、ルールのどれかでファイルの一致が true になると、結果は true になります。

上記のルールでは、ファイル「images/profilePhoto.png」は condition または other_condition が true になると、読み取り可能になります。これに対して、ファイル「images/users/user:12345/profilePhoto.png」に影響する条件は other_condition の結果だけです。

ワイルドカード変数は、match の指定ファイル名またはパス認証から参照できます。

// Another way to restrict the name of a file
match /images/{imageId} {
  allow read: if imageId == "profilePhoto.png";
}

Cloud Storage セキュリティ ルールはカスケード処理されません。ルールは、リクエストパスと、ルールで指定されているパスが一致する場合のみ評価されます。

リクエストの評価

アップロード、ダウンロード、メタデータの変更、削除は、Cloud Storage に送信される request を使用して評価されます。request 変数には、リクエストが行われるファイルパス、リクエストの受信時間、新しい resource 値(リクエストが書き込みの場合)に加え、HTTP ヘッダーと認証状態も含まれます。

また、request オブジェクトには、ユーザーの一意の ID と request.auth オブジェクト内の Firebase Authentication ペイロードも含まれます。これらについては、このドキュメントの認証セクションで詳しく説明します。

request オブジェクトに含まれるプロパティの一覧は次のとおりです。

プロパティ 説明
auth マップ<文字列, 文字列> ユーザーがログイン済みの場合、uid(ユーザーに一意の ID)と token(Firebase Authentication JWT クレームのマップ)を提供します。それ以外の場合は、null になります。
params マップ<文字列, 文字列> リクエストのクエリ パラメータを含むマップです。
path パス リクエストの実行先のパスを表す path です。
resource マップ<文字列, 文字列> write リクエスト時にのみ指定される新しいリソース値です。
time タイムスタンプ リクエスト評価時のサーバー時刻を表すタイムスタンプです。

リソースの評価

ルールを評価するときに、アップロード、ダウンロード、変更、または削除するファイルのメタデータも評価する必要がある場合があります。メタデータを評価すると複雑で効果的なルールを作成できるので、特定のコンテンツ タイプを持つファイルだけをアップロードしたり、特定のサイズよりも大きなファイルだけを削除したりすることができます。

Cloud Storage 用の Firebase Storage セキュリティ ルールにおいて、ファイルのメタデータは resource オブジェクトを通して提供されます。このオブジェクトには、Cloud Storage オブジェクトに示されるメタデータの Key-Value ペアが含まれます。read または write リクエストでこれらのプロパティを検査することで、データの整合性を確認できます。

write リクエスト(アップロード、メタデータの更新、削除など)では、現在リクエストパスに存在しているファイルのファイル メタデータを含む resource オブジェクトに加え、書き込みが許可された場合に書き込まれるファイル メタデータのサブセットを含む request.resource オブジェクトを使用することもできます。この 2 つの値を使用してデータの整合性を確認したり、ファイルのタイプやサイズといったアプリケーションの制約を適用したりできます。

resource オブジェクトのプロパティの完全なリストは次のとおりです。

プロパティ 説明
name 文字列 オブジェクトのフルネームです。
bucket 文字列 このオブジェクトが置かれているバケットの名前です。
generation 整数 このオブジェクトの Google Cloud Storage オブジェクト生成です。
metageneration 整数 このオブジェクトの Google Cloud Storage オブジェクト メタ生成です。
size 整数 オブジェクトのサイズです(バイト単位)。
timeCreated タイムスタンプ オブジェクトの作成時刻を示すタイムスタンプです。
updated タイムスタンプ オブジェクトの最終更新時刻を示すタイムスタンプです。
md5Hash 文字列 オブジェクトの MD5 ハッシュです。
crc32c 文字列 オブジェクトの crc32c ハッシュです。
etag 文字列 このオブジェクトに関連付けられた etag です。
contentDisposition 文字列 このオブジェクトに関連付けられたコンテンツの配置です。
contentEncoding 文字列 このオブジェクトに関連付けられたコンテンツ エンコーディングです。
contentLanguage 文字列 このオブジェクトに関連付けられたコンテンツ言語です。
contentType 文字列 このオブジェクトに関連付けられたコンテンツ タイプです。
metadata マップ<文字列, 文字列> デベロッパーがカスタム メタデータで指定した、追加の Key-Value ペアです。

request.resource は、generationmetagenerationetagtimeCreatedupdated を除いて、上記のすべてを含みます。

セキュリティ ルールの制限

セキュリティ ルールに関する操作では、次の制限に注意してください。

制限 詳細
リクエストあたりの firestore.exists()firestore.get() 呼び出しの最大数

単一ドキュメントに対するリクエストとクエリ リクエストの場合は 2。

この上限を超えると、アクセス拒否のエラーが発生します。

同じドキュメントへのアクセス呼び出しはキャッシュされる場合があります。キャッシュされた呼び出しは制限数に計上されません。

これまでの説明をすべてまとめると、画像ストレージ ソリューションとして次の完全なサンプルルールを作成できます。

service firebase.storage {
 match /b/{bucket}/o {
   match /images {
     // Cascade read to any image type at any path
     match /{allImages=**} {
       allow read;
     }

     // Allow write files to the path "images/*", subject to the constraints:
     // 1) File is less than 5MB
     // 2) Content type is an image
     // 3) Uploaded content type matches existing content type
     // 4) File name (stored in imageId wildcard variable) is less than 32 characters
     match /{imageId} {
       allow write: if request.resource.size < 5 * 1024 * 1024
                    && request.resource.contentType.matches('image/.*')
                    && request.resource.contentType == resource.contentType
                    && imageId.size() < 32
     }
   }
 }
}

Realtime Database

基本構造

Realtime Database では、Firebase セキュリティ ルールは JavaScript のような式で構成されます。これらの式は JSON ドキュメントに格納されます。

ルールでは次の構文を使用します。

{
  "rules": {
    "<<path>>": {
    // Allow the request if the condition for each method is true.
      ".read": <<condition>>,
      ".write": <<condition>>,
      ".validate": <<condition>>
    }
  }
}

ルールには次の 3 つの基本要素があります。

  • パス: データベースの場所。これにはデータベースの JSON 構造が反映されます。
  • リクエスト: ルールでアクセス権を付与するために使用するメソッド。read ルールと write ルールは広範な読み取りや書き込みアクセス権を付与します。一方、validate ルールは 2 次検証として機能し、受信データまたは既存のデータに基づいてアクセス権を付与します。
  • 条件: true と評価された場合にリクエストを許可する条件です。

ルールがパスに適用される仕組み

Realtime Database では、セキュリティ ルールがアトミックに適用されます。つまり、上位の親ノードのルールで、細分化した子ノードのルールがオーバーライドされ、より深いノードのルールでは親パスへのアクセス権を付与できません。親パスのいずれかへのアクセス権をすでに付与している場合、データベース構造内のより深いパスへのアクセスの絞り込みや取り消しはできません。

次のルールを見てみましょう。

{
  "rules": {
     "foo": {
        // allows read to /foo/*
        ".read": "data.child('baz').val() === true",
        "bar": {
          // ignored, since read was allowed already
          ".read": false
        }
     }
  }
}

このセキュリティ構造では、/foo/ に値が true の子 baz が含まれている場合に、/bar/ を読み取ることができます。ここでは、/foo/bar/ の下にある ".read": false ルールは影響しません。子パスではアクセス権を取り消すことができないからです。

直観的ではないかもしれませんが、これはルール言語の優れた利点であり、これにより、最小限の労力で、非常に複雑なアクセス権限を実装できます。これはユーザーベースのセキュリティの場合に、とりわけ役に立ちます。

ただし、.validate ルールはカスケード処理されません。書き込みが許可されるためには、階層のすべてのレベルで、すべての検証ルールが一致する必要があります。

さらに、リクエストされた場所やアクセス権を付与する親の場所にルールが存在しない場合、読み取りや書き込みオペレーションは失敗します。ルールが親パスに遡って適用されることがないためです。対象のすべての子パスにアクセスできる場合でも、親の場所での読み取りは完全に失敗します。次の構造を見てみましょう。

{
  "rules": {
    "records": {
      "rec1": {
        ".read": true
      },
      "rec2": {
        ".read": false
      }
    }
  }
}

ルールがアトミックに評価されることを理解していない場合、/records/ パスをフェッチすると rec1 が返される一方で rec2 は返されないと考えるかもしれません。しかし、実際の結果はエラーになります。

JavaScript
var db = firebase.database();
db.ref("records").once("value", function(snap) {
  // success method is not called
}, function(err) {
  // error callback triggered with PERMISSION_DENIED
});
Objective-C
注: この Firebase プロダクトは、App Clip ターゲットでは使用できません。
FIRDatabaseReference *ref = [[FIRDatabase database] reference];
[[_ref child:@"records"] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
  // success block is not called
} withCancelBlock:^(NSError * _Nonnull error) {
  // cancel block triggered with PERMISSION_DENIED
}];
Swift
注: この Firebase プロダクトは、App Clip ターゲットでは使用できません。
var ref = FIRDatabase.database().reference()
ref.child("records").observeSingleEventOfType(.Value, withBlock: { snapshot in
    // success block is not called
}, withCancelBlock: { error in
    // cancel block triggered with PERMISSION_DENIED
})
Java
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference ref = database.getReference("records");
ref.addListenerForSingleValueEvent(new ValueEventListener() {
  @Override
  public void onDataChange(DataSnapshot snapshot) {
    // success method is not called
  }

  @Override
  public void onCancelled(FirebaseError firebaseError) {
    // error callback triggered with PERMISSION_DENIED
  });
});
REST
curl https://docs-examples.firebaseio.com/rest/records/
# response returns a PERMISSION_DENIED error

/records/ での読み取りオペレーションはアトミックである上、/records/ の下にあるすべてのデータへのアクセス権を付与する読み取りルールが存在しないため、PERMISSION_DENIED エラーがスローされます。Firebase コンソールのセキュリティ シミュレータでこのルールを評価すると、読み取りオペレーションが拒否されたことがわかります。

Attempt to read /records with auth=Success(null)
    /
    /records

No .read rule allowed the operation.
Read was denied.

/records/ パスに対する読み取りアクセスを許可するルールが存在しなかったため、このオペレーションは拒否されました。ただし、リクエストしたパスに rec1 のルールが存在しなかったため、評価されることがなかったことに注意してください。rec1 をフェッチするには、それに直接アクセスする必要があります。

JavaScript
var db = firebase.database();
db.ref("records/rec1").once("value", function(snap) {
  // SUCCESS!
}, function(err) {
  // error callback is not called
});
Objective-C
注: この Firebase プロダクトは、App Clip ターゲットでは使用できません。
FIRDatabaseReference *ref = [[FIRDatabase database] reference];
[[ref child:@"records/rec1"] observeSingleEventOfType:FEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
    // SUCCESS!
}];
Swift
注: この Firebase プロダクトは、App Clip ターゲットでは使用できません。
var ref = FIRDatabase.database().reference()
ref.child("records/rec1").observeSingleEventOfType(.Value, withBlock: { snapshot in
    // SUCCESS!
})
Java
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference ref = database.getReference("records/rec1");
ref.addListenerForSingleValueEvent(new ValueEventListener() {
  @Override
  public void onDataChange(DataSnapshot snapshot) {
    // SUCCESS!
  }

  @Override
  public void onCancelled(FirebaseError firebaseError) {
    // error callback is not called
  }
});
REST
curl https://docs-examples.firebaseio.com/rest/records/rec1
# SUCCESS!

位置変数

Realtime Database セキュリティ ルールでは、$location 変数を使用したパスセグメントの照合をサポートしています。パス上にある子ノードとルールを照合するには、該当するパスセグメントの前に $ 接頭辞を付加します。

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

$variable と定数パス名を併用することもできます。

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