Realtime Databeseでのルール指定方法について、公式ドキュメントに一応載っていますが、実戦的に使用するには言葉足らずなところがあるので、具体例を用いながら説明していきます。
これで、あなたも希望したセキュリティのデータベースやアプリケーションを作れる!(はず)
前提
- Firebase JavaScript SDK version9を使用(厳密にはバージョン9.8.1)
- Google認証でのログイン機能を実装
- chrome(バージョン: 101.0.4951.67)
Realtime Databeseの「ルール」とは?
Realtime Databeseの「ルール」では、どんな人ならデータベースに読み書きできるかや、データベースでのインデックス作成(※)等を決めることができます。
※データベースのインデックスについては、大きなデータベースを作らない場合は気にしないで大丈夫です。詳しく知りたい方はこちらの記事などが参考になるかと思います。
https://qiita.com/Hashimoto-Noriaki/items/bd078d0fabd4a737a971
まだ意味を理解していなくても大丈夫ですが、以下のように書きます。
{
"rules": {
"users": {
"$uid": {
".write": "$uid === auth.uid",
".read": "$uid === auth.uid",
}
}
}
}
習うより慣れろ!まずはロックモードとテストモードの意味を理解する!
FirebaseでRealtime Databeseを一度は触ったことがある方なら、以下のようなロックモードとテストモードを選んだことがあるかと思います。
ロックモードとテストモードの際のルールはそれぞれ以下です。
{
"rules": {
".write": false,
".read": false
}
}
{
"rules": {
".write": true,
".read": true
}
}
これらのルールは実は
・ロックモード:誰もデータに読み書きできない
・テストモード:誰でもデータを読み書きできる
との意味です。
.readで、どんなユーザーがデータの読み取りをできるかを指定しています。.writeで、どんなユーザーがデータを記載できるかを指定しています。
falseということはどんなユーザーにも許可していないという意味です。逆にtrueならば、どんなユーザーに対しても許可していることになります。
何気なく選んでいたロックモードとテストモードの意味について理解したところで、次は$変数($プレフィックス)を用いた例を説明します。
$変数を用いて、特定のパスに関するルールを指定する
$プレフィックスを用いてキャプチャ変数を宣言すれば、パスの一部をキャプチャできます。「キャプチャ」とは、パスの一部を変数のようなものにできるということだとお考え下さい。
パスはデータベースのディレクトリ構造のようなものだと思っていただければよくて、普段使用しているパソコンで、ドキュメントフォルダの下に「Firebase学習」といったフォルダを作ることで、/ドキュメント/Firebase学習/となっているようなものだとお考え下さい。
Firebase公式ドキュメントにもある例を用いてご説明します。
{
"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')"
}
}
}
}
}
これは/rooms/というパスの配下の/$room_id/の下の/topic/の下のパスのデータに関して、".write"、つまり書き込みルールを指定をしています。
要は/rooms/〇〇〇/topic/というパスの下にあるデータの読み書きをどうするか、ということで、今回の場債は〇〇〇の中に'public'という単語が含まれる場合は、書き込めるということです。
ユーザー認証・ログイン機能と$変数を用いて、ユーザーごとに書き込めるディレクトリを変える
Firebase公式ドキュメントから、認証機能のあるアプリでよく用いるルール指定方法を引用してご説明します。
{
"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"
}
}
}
}
これは先ほど$変数について学んだ知識を活かすと、/users/〇〇〇/配下のデータの書き込めるユーザーのルールを指定していることが分かります。〇〇〇がauth.uidと同じであれば書き込めるということですが、auth.uidは認証機能を用いるとFirebaseが生成してくれるユーザーIDです。
このユーザーIDは以下のようなコードを書けば、Firebase ウェブ バージョン9を用いたHTMLやJSファイルで取得することができます。
//###############################################
// 必要なJSを読み込み
//###############################################
import { initializeApp } from "https://www.gstatic.com/firebasejs/9.4.1/firebase-app.js";
import {
getDatabase,
ref,
push,
set,
onChildAdded,
remove,
onChildRemoved,
} from "https://www.gstatic.com/firebasejs/9.4.1/firebase-database.js";
import {
getAuth,
signInWithPopup,
GoogleAuthProvider,
signOut,
onAuthStateChanged,
} from "https://www.gstatic.com/firebasejs/9.4.1/firebase-auth.js";
//###############################################
//FirebaseConfig
//###############################################
const firebaseConfig = {
apiKey: "AIzaSyDxTNT1PjBzczI9M2teRW-oM8NhF2RdjPg",
authDomain: "fir-auth-5913e.firebaseapp.com",
databaseURL: "https://fir-auth-5913e-default-rtdb.firebaseio.com",
projectId: "fir-auth-5913e",
storageBucket: "fir-auth-5913e.appspot.com",
messagingSenderId: "656896118732",
appId: "1:656896118732:web:5d21d485236748079ed769",
};
const app = initializeApp(firebaseConfig);
//###############################################
//Firebase-RealtimeDatabase接続
//###############################################
const db = getDatabase(app); //RealtimeDBに接続
//###############################################
//GoogleAuth用
//###############################################
const provider = new GoogleAuthProvider();
provider.addScope("https://www.googleapis.com/auth/contacts.readonly");
const auth = getAuth();
//###############################################
//Loginしていれば処理します
//###############################################
onAuthStateChanged(auth, (user) => {
if (user) {
// User is signed in, see docs for a list of available properties
// https://firebase.google.com/docs/reference/js/firebase.User
const uid = user.uid;
const dbRef = ref(db, "users/" + uid); //dbRefのパスにはuidが含まれている
//データ登録(Click)
//JQueryを読み込んだHTML上のidがsendのボタンを押すと、以下の処理が走るようになっている
$("#send").on("click", function () {
const msg = {
uname: $("#uname").val(),
text: $("#text").val(),
};
//Pushできる状況を作っている。dbRefには、/users/以下のパスにuidが含まれているので、書き込める
const newPostRef = push(dbRef);
set(newPostRef, msg); //DBに値をセットする
});
} else {
_redirect(); // User is signed out
}
});
function _redirect() {
location.href = "login.html";
}
_redirectは、ログイン画面にリダイレクトするように別途作成した関数です。
データの読み取りをログインユーザーに許可する
上の状態ではデータの読み取りについて何も書かれていないです。私が実験したところ、".read"について特に記述をしていない場合は、勝手に".read": false
を指定していることになってしまい、データの読み取りができなくなってしまうようです。
こういったルールをしている・していない・親に指定している・していないといった事項は、Firebase公式ドキュメントの読み取りおよび書き込みルールカスケードに詳しい情報が書かれています。
データの読み取りをログインユーザーのみが行えるようにするには、以下のように.readについても同じ記述をすればよいです。
{
"rules": {
"users": {
"$user_id": {
// grants write and read access to the owner of this user account
// whose uid must exactly match the key ($user_id)
".write": "$user_id === auth.uid"
".read": "$uid === auth.uid",
}
}
}
}
また、読み取ったデータをHTMLに表示するためには、先ほどのユーザーIDを取得するJavascript.js
を書き換えなくてはいけません。
onChildAddedを用いる場合、ログインユーザーしかデータを読み取れない関係で、onAuthStateChanged
の中のif(user)
内に書かないといけないみたいです。(私も厳密には理解していないのです。。。)
onAuthStateChanged(auth, (user) => {
if (user) {
// ここにデータを読み取って表示するためのonChildAddedを書く
// onChildAdded(dbRef, function (data) {
// 何かしらの処理
// });
}
}
どこまでの深さのパスまでルールを指定できているか
先ほどの認証機能付きアプリでよく用いるルール2
の書き方で、`/users/${uid}/`
パス配下の読み書きをログインユーザーのみに絞れている点は分かりました。
{
"rules": {
"users": {
"$user_id": {
// grants write and read access to the owner of this user account
// whose uid must exactly match the key ($user_id)
".write": "$user_id === auth.uid"
".read": "$uid === auth.uid",
}
}
}
}
では、`/users/${uid}/chat/`
や`chat/users/${uid}/`
には読み書きできるのでしょうか?結論、前者の読み書きはできますが、後者ではできません。
後者でデータを書き込もうとした場合、
logger.ts:115 [2022-05-18T04:45:24.234Z] @firebase/database: FIREBASE WARNING: set at /chat/users/Ftf3kssqRjNKPteQ6xvYkvXQekg1/-N2KU1HLYbkiuSc12ZgP failed: permission_denied
等、permission_deniedと言われます。
Firebase公式ドキュメントのルールについての個所に詳細は記載されています。
以上!