Firebase Cloud Firestoreクライアント側からのデータ追加とセキュリティルール設定

FirebaseのデータベースのFirestoreですが、クライアントサイドからのアクセスで直接データのやり取りができるので非常に便利です

ただ、モバイルアプリケーションやウェブアプリケーションで利用する際はセキュリティルールを設ける必要があります

今回はFirestoreへのデータ追加(ウェブSDKバージョン9)

なぜセキュリティルールが必要なのか

Firestoreの初期状態ではセキュリティルールは設定されていません

誰でもデータベースを見れるし誰でもデータを追加や削除、更新することができる状態にあります

当然そのようなアプリケーションは使うことができません

そこで利用されるのがセキュリティルールです

Firestoreではセキュリティルールを設定してアクセス制御とデータ検証いわゆるバリデーションを実装することができます

クライアント側の処理でガードできるのではないか

もうセキュリティールールの必要性に関して1点疑問に思う方もいらっしゃるかもしれません

それは、クライアントの処理で認証状態を把握してアクセス制限をかけたり、意図せぬデータ処理が行われないかどうかバリデーションの処理を実装しているのではないかということです

たしかに、今回プロジェクトでしようしているVueRouterではログインしていなければアクセスできないようにルーティングの処理をほどこしていました(くわしくはこちらから)

アプリケーションを作成する際に当然その利用する内容に合わせたデータ構造と、アクセス制限を考えてそれをクライアント側で最も最適に使用できるようなUIを作成します

ただしそれはあくまで正規にそのアプリケーションを利用してくださるサービス利用者への快適かつ最適なサービスを展開するためのクライアント処理ということになります

Firestoreのデータベースへはクライアント側を認識して処理を行っているわけではありませんので、アプリケーションの運営者以外がクライアント側の処理を作成してデータベースの中身にアクセスすることは可能です

そのため、Firestoreのセキュリティルールを設定して、運営者が意図せぬデータベースの処理をさせないための仕組みがひつようになるわけです

セキュリティルールの設定手順

ではセキュリティルールの設定はどのようにするべきなのか

まずはどのようなデータベースの構成になるのかを考える必要があります

その構成によって必要なセキュリティルールを設定していくことになります

今回はユーザー情報を管理するCollectionの作成をします

users collection

documentID:ユーザーuid (Authenticationに登録されているuidと同じ)

username: String,   

created_at: Timestamp(サーバーの時刻),

belonging: String

作成の方法は

ログイン時(アカウント作成時)に自動的にアカウントの情報をFirestoreに作成するようにします

60行目以下がその処理の部分になります

62行目のonAuthStateChanged(auth, async (user) => {…}

ここで認証処理の何らかの変化が行われたときに認証状態のチェックを行います

63行目のif文でユーザー情報が存在するかどうかを確認してログイン状態にあるかどうかをチェックします

(!user)は存在していないということになりますので特に処理は施していません

ログイン状態にあるばあいはelse以下の処理を行います

69行目のconst docRef = doc(db, “users”, user.uid)でデータベースの参照先を

doc()メソッドの書き方

doc(db,”コレクション名”,ドキュメントID)

※サンプルでは14行目でconst db = getFirestore();


70行目のconst userDoc = await getDoc(docRef)でその参照先にあるデータの取得をしています

getDocで単一ドキュメントの取得ができます

getDoc()メソッドの書き方

getDoc(参照先)

今回は参照先を doc(db, “users”, user.uid)としていますので

usersコレクションの中からuser.uid(FirebaseAuthenticationのユーザーuid)をドキュメントIDとするデータを参照しています

そして71行目以降のif (!userDoc.exists()) {…}でuserDocに対してexists()を実行して取得したドキュメントの存在の有無を確認しています

この場合は”!”を付けていますので、存在しない場合の処理をその後に書いています

今回は、ユーザーの情報をユーザーがログインした際に作成しようとしています

すでに作成済みの場合は当然ですがユーザー情報を改めてデータベースに追加する必要はありません

ですので、「存在していない場合」にデータベースのusersコレクションへデータを追加する処理を施しています

73行目以降のsetDoc(){…}でデータベースへ追加ができます

setDoc()の書き方

setDoc(参照先){追加するデータ}

usersコレクションを作成していないのではないか?と思われた方もいらっしゃるかもしれませんが

Firestoreでは、存在していない場合は新たに作成されます

※今回はドキュメントIDをユーザーのuidとしていますでsetDocでOKですが、ドキュメントIDを自動採番したい場合はaddDoc()を使用します

今回の例で追加されたデータは

{
userID: user.uid,
username: ‘no name’,
created_at: Timestamp.now(),
belonging:’no belonging’
});

となっています

userIDはドキュメントIDと同じでユーザーのuidを追加しています(ドキュメントIDは検索できないのでここでここで持たせておくとよい)

usernameはいったんno nameとしておき、後ほどユーザー設定から変更する形をとりたいと思います

create_atはサーバーの現在時刻です

belongingはいったんno belongingとしておき、こちらもusernameと同様の処理をしようと思います

セキュリティルールの作成方法

ではいよいよセキュリティルールの作成方法です

セキュリティルールの作成方法は2種類あります

①Firebaseのコンソール内で直接記述する方法と、

②作成したルールをFirebaseCLIを利用してデプロイする方法です

私は②の方法でVScode上で作成したルールをデプロイすることにしました

FirebaseCLIのインストール

まずはFirebaseCLIをインストールします

ターミナルで

>npm install -g firebase-tools

を実行します

そして、CLIのコマンドをつかってfirebaseにログインします

同じくターミナルで

>firebase login

を実行し、プロジェクトに使用しているGoogleアカウントを選択します

アプリの作業ディレクトリ内で以下を実行し初期化をします

>firebase init

その後、アプリの作業ディレクトリ内にfirebase.jsonとfirestore.rulesが作成されます

ルールはfirestore.rulesに書いていくことになります

再生後にプロジェクトにデプロイするときは以下のコマンドを実行します

>firebase deploy –only firestore:rules

コンソールを立ち上げてルールを確認してみると、更新されていることが分かります

セキュリティルールの書き方

今回作成したルールです

1行目はルールのバージョンです

1と2が現在存在しています

今回は2を使用します

2行目でルールを設定するサービスを指定します

3行目にmatch(データベースの識別)がありますが、ここはこのような書き方をするということでOKです

ですのでFirestoreのセキュリティルールを書く際に1~3行目はそういうものだと理解すればよいと思います

11行目からが具体的なルールの書き方になります

match /(コレクション名)users/(ドキュメントID){userID}でデータの識別を行っています

ようするにどの部分に対するルールなのかということです

そして12行目にある

allow create(update,deleteなど):if条件にてそのドキュメントへのアクセス制御をおこなっています

今回はデータの追加についてなのでcreateについての制御をしているということです

functionを利用してルールを簡略化しよう

よく使うルールはfunctionを作って利用することができます

例では4~9行目のところにある

function isAuthenticated(){
return request.auth !=null;
}
function isUserAuthenticated(userID){
return isAuthenticated() && userID == request.auth.uid
}

この部分です

名称は何でもよいのですが、今回はisAuthenticated()とisUserAuthenticated(userID)としました

引数も設定できます

isAuthenticated()でログイン状態にあるかどうかを確認し、request.auth !=null リクエストの認証がnullではない場合Trueを返します 

要するにTrueが返されたときはログインしているという状態を表します

isUserAuthenticated(userID)は先に作成したisAuthenticatied()がTrueかつ引数に入れられたuserID(この場合のuserIDはmatch /users/{userID}でドキュメントIDとして設定されているuserID)がrequest.auth.uidと同じである場合にTrueを返すようになります

※Javascriptの関数に似ていますが、returnで返せるのは単体の回答のみです

要するにこの二つのfunctionでは認証状態(ログインしているかどうか)ということと、参照しようとしているのが本人かどうかを確認するものになります

このようによく使いそうな条件はfunctionで設定しておくとルールが複雑にならずにすみます

ということで、今回の例で説明をすると

allow create:if isUserAuthenticated(userID)
&& (!(‘username’ in request.resource.data) || (request.resource.data.username is string && request.resource.data.username.size()<=10))
&& request.resource.data.created_at – duration.value(5,’s’) <= request.time //created_at の5秒後 が requestの時間以下
&& request.resource.data.belonging is string;

createが許される条件は

isUserAuthenticated(userID)がTrueで

(!(‘username’ in request.resource.data))||~ややこしい書き方ですが、

request.resource.dataはリクエスト内のデータになります

今回でいうとsetDocによるリクエストの中身が入っているとおもってください

その中にusernameが入っているかどうかということです !を付けていますので入っていなければ問題なしです

入っている場合は||の右側の条件を満たす必要があります

(request.resource.data.username is string && request.resource.data.username.size()<=10)

となりますのでusernameがstringかつ10字以内ということになります

左||右 この場合、

左または右の条件を満たす必要があるという意味になります

request.resource.data.created_at – duration.value(5,’s’) <= request.timeの部分は

create_atの時刻がリクエストしてきた時間の5秒以内かどうかを確認しています

そして最後の

request.resource.data.belonging is string;

で、belongingがstringであることを条件としています

作成が完了したら保存をしてデプロイをしましょう

こちらが動画になります

コメント

タイトルとURLをコピーしました