セキュリティは、アプリ開発というパズルにおいて最も複雑なピースのうちの 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 つ以上のメソッド。これらのメソッドに対して実行を許可します。標準メソッドはget
、list
、create
、update
、delete
です。read
とwrite
は、指定したデータベースまたはストレージのパスへの広範な読み取りや書き込みアクセスを可能にするコンビニエンス メソッドです。 - パス: データベースまたはストレージの場所。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
変数は SF
や NYC
などの 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() 呼び出しの最大数 |
いずれかの上限を超えると、アクセス拒否のエラーが発生します。 一部のドキュメントに対するアクセス呼び出しはキャッシュされる場合があります。キャッシュされた呼び出しは上限数に計上されません。 |
ネストされた match ステートメントの深さの最大数 |
10 |
ネストされた一連の match ステートメント内で許可されるパスセグメント内の最大パス長 |
100 |
ネストされた一連の match ステートメント内で許可されるパスキャプチャ変数の最大数 |
20 |
関数呼び出しの深さの最大数 | 20 |
関数引数の最大数 | 7 |
関数あたりの let 変数バインディングの最大数 |
10 |
関数の再帰的な呼び出し、または循環的な呼び出しの最大数 | 0(許可されていません) |
リクエストあたり評価される式の最大数 | 1,000 |
ルールセットの最大サイズ | ルールセットは、次の 2 つのサイズ上限に従う必要があります。
|
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 つ以上のメソッド。これらのメソッドに対して実行を許可します。標準メソッドはget
、list
、create
、update
、delete
です。read
とwrite
は、指定したデータベースまたはストレージのパスへの広範な読み取りや書き込みアクセスを可能にするコンビニエンス メソッドです。 - パス: データベースまたはストレージの場所。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
は、generation
、metageneration
、etag
、timeCreated
、updated
を除いて、上記のすべてを含みます。
セキュリティ ルールの制限
セキュリティ ルールに関する操作では、次の制限に注意してください。
制限 | 詳細 |
---|---|
リクエストあたりの 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
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
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
FIRDatabaseReference *ref = [[FIRDatabase database] reference]; [[ref child:@"records/rec1"] observeSingleEventOfType:FEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) { // SUCCESS! }];
Swift
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 }
}
}
}