350
287

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

FirebaseAdvent Calendar 2016

Day 5

firebase realtime database でよく使う rule

Last updated at Posted at 2016-12-04

概要

firebase realtime database (以下 database) には rule という仕組みがあり、これを用いることである程度のデータ構造(RDB で言う所のテーブル構造)を定義したり、特定のデータへのアクセス制限をしたりすることができます。

基本的な概念や使い方は公式ページが日本語化されているのそちらを参照してください。

ノウハウ集

firebase を使って4つほどアプリケーションを作ってみてよく使った rule やこうした方が良いみたいなパターンを書いていきたいと思います。

認証しているユーザーは全てのデータにアクセス許可

database の rule はデフォルトでは以下のように認証済みのユーザーは全てのデータに read, write の権限を持っています。

auth というのは実は変数で認証していると uid などのプロパティを持つオブジェクトになっています。

セキュリティなどが心配ですがプロジェクトの最初の段階ではこの状態でガシガシ開発していっても良いと思います。

{
  "rules": {
    ".read": "auth != null",
    ".write": "auth != null"
  }
}

全部拒否 (おすすめの設定)

これは僕のおすすめの設定です。

デフォルトの状態のまま使うのを止めて以下のように read, write を false にして全てのデータにアクセスできなくします。

{
  "rules": {
    ".read": false,
    ".write": false,
  }
}
// この状態でも nodejs 等の admin 用ライブラリではデータにアクセスすることができます。

こうしておくことで以下のような利点が生じます。

  • 明示的にアクセスを許可したデータだけユーザーがアクセスできるようになるのでセキュリティが高くなる
  • データを保存する際に key を typo してもその key には書き込み権限が無いとエラーが返ってくるのでプログラムのバグを防げる
    • ex) texttest と typo したときなど

ちなみに rule に何も書かないと上記と同じ意味になります。

特定の key だけ認証済みユーザーは許可

アプリケーションを作る上では root で全て拒否しておいて、任意のデータにだけアクセスは許可したいというのが基本的な使い方だと思いますのでこれは絶対に抑えておきましょう。

以下の例では users 以下のデータには認証済みユーザーがアクセスできるという例です。

{
  "rules": {
    "users": {
      ".read": "auth != null",
      ".write": "auth != null"
    }
  }
}

bolt で書くとこうです。

path /users {
  read() { auth != null }
  write() { auth != null }
}

または以下のようにすると userId の指定を忘れて users 直下にデータを書き込んじゃったみたいなミスが無くなります。

$userId組み込み変数と言われるものの一種で $ 以降の userId の部分はただの変数名なのでなんでも大丈夫です。

{
  "rules": {
    "users": {
      "$userId": {
        ".read": "auth != null",
        ".write": "auth != null"
      }
    }
  }
}

bolt で書くとこうです。 ネストが深くならなくていいですね。

path /users/{userId} {
  read() { auth != null }
  write() { auth != null }
}

自分のデータにだけアクセス許可

先の例 (users) では他のユーザーの情報もアクセスが許可されていますが、自分のデータだけしかアクセス許可しないというシーンもあると思います。

この場合は組み込み変数の $userId を利用して以下のように auth.uid == $userId とすると自分のデータのときだけアクセスできるようになります。

{
  "rules": {
    "user-data": {
      "$userId": {
        ".read": "auth.uid == $userId",
        ".write": "auth.uid == $userId",
      }
    }
  }
}

bolt で書くとこうです。

path /user-data/{userId} {
  read() { auth.uid == userId}
  write() { auth.uid == userId }
}

書き込みできるのは自分のデータだけ

書き込みと読み込みのアクセス権限を分けることももちろんできます。

以下の例は読み込みは認証済みのユーザーなら誰でも出来るけど、書き込みは自分だけできるというアクセス権限です。

{
  "rules": {
    "user-data": {
      ".read": "auth != null",
      "$userId": {
        ".write": "auth.uid == $userId",
      }
    }
  }
}

管理者ユーザーなら読み書きできる

以下のようなデータがあったとして、 uid_10 のデータに本人と管理者ユーザー(uid_1 or uid_2)がアクセスできるという権限を作る方法です。

管理画面などで特権ユーザーの場合にはなんでもできるようにしたいというシーンです。

{
  "admin-users": {
    "uid_1": true,
    "uid_2": true,
  },
  "user-data": {
    "uid_10": {
       "hoge": "huga"
     }
  }
}

書き込み権限の root.child('admin-users').child(auth.uid).exists()" の部分がミソです。

root という組み込み変数で database の root の RuleDataSnapshot というオブジェクトが取得できます。 これを使うと query を発行できるので他の key のデータを参照してロジックを組むことができます。

今回の場合は admin-users に現在の user の uid があったら管理者でログインしているとみなして書き込みを許可するというロジックを組んでいます。

rule の中にコードが書けてすごい便利です。

{
  "rules": {
    "user-data": {
      ".read": "auth != null",
      "$userId": {
        ".write": "auth.uid == $userId || root.child('admin-users').child(auth.uid).exists()",
      }
    }
  }
}

user-data に限らずなんでも出来るようにする場合は、 rules のルートに書いても良いです。

{
  "rules": {
    ".read": "root.child('admin-users').child(auth.uid).exists()",
    ".write": "root.child('admin-users').child(auth.uid).exists()",
    "user-data": {
      ".read": "auth != null",
      "$userId": {
        ".write": "auth.uid == $userId",
      }
    }
  }
}

これらの方法はデータのアクセス制限を作る上でめちゃめちゃよく使うパターンです。

特定のデータは必須にする

例えば users の中のデータには full_namecreated_at が必須という制限を設けたいときは ".validate" を使用して以下のように記述します。

{
  "rules": {
    "users": {
      "$userId": {
        ".read": "auth != null",
        ".write": "auth != null",
        ".validate": "newData.hasChildren(['full_name', 'created_at'])"
      }
    }
  }
}

型を限定する

型と言っても js なのでそんなに厳密に扱えるわけではありませんが、 isNumber()isString()isBoolean() を使うことができます。 詳細はこのあたりを参照してください。

以下の例では full_name は文字列であるという制限を設けています。

{
  "rules": {
    "users": {
      "$userId": {
        ".read": "auth != null",
        ".write": "auth != null",
        "full_name": {
          ".validate": "newData.isString()"
        }
      }
    }
  }
}

いずれかの値に制限する (enum のような使い方)

ある key に入るデータはある集合のうちのどれか1つをとるという制限を設けたい場合があります。

以下の例ではユーザーの typeprivate または public のどちらかにしたいという制限を設けています。

{
  "rules": {
    "users": {
      "$userId": {
        ".read": "auth != null",
        ".write": "auth != null",
        "type": {
          ".validate": "(newData.val() === 'private' || newData.val() === 'public')"
        }
      }
    }
  }
}

インデックスを張る

database にはインデックスを貼ることができます。 インデックスは value を使用したクエリーを発行する場合に適切に設定されていないと警告が出るので、それが出たら設定するという感じでいいと思います。

firebase は非常に賢いです。

インデックスは以下のように .indexOn で設定します。

{
  "rules": {
    "users": {
      "$userId": {
        ".read": "auth != null",
        ".write": "auth != null",
        ".indexOn": ["full_name"]
      }
    }
  }
}

まとめ

様々なケースを例に上げて rule を示しました。

firebase database は比較的新しいサービスなだけに rule の例がそんなに多くないので参考になれば幸いです。

他にも思いついたら追記していきたいと思います。

追記

realtime database の rule は json でそのまま書くと記述量が多く大変になります。

下記の Bolt Compiler を使って記述すると短く書けてとても良いのでお試しください。

350
287
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
350
287

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?