Firestoreのエンドポイントをライブラリを使わずに使用する方法

サーバーを安定稼働させるにはお金がかかりすぎるので、最近趣味開発でFirestoreを使用し始めました。
無料枠が大きくてとても良いですね・・・もっと早くこっちをやればよかったのです。
Firestoreを使う中で感じたのですが、ライブラリを用いてFirestoreを使う方法は沢山ヒットするわりに生のリクエストを送る方法は全然ヒットしないなと困り果てました。
※生のリクエストを送るというのは、例えばGETメソッドを指定して、あるエンドポイントにクエリパラメータを指定してリクエストを投げるという、一連の流れをさしています

知っていれば簡単なのですが、知らなきゃめんどくさいので書き留めておきます。

#### この記事で話すこと
- GET, POST, PATCH, DELETEメソッド、それぞれにおけるリクエストの投げ方
- 認証とセキュリティルールについて（Firebase authenticationを使っている場合）


それでは早速・・・

# GET, POST, PATCH, DELETEメソッド、それぞれにおけるリクエストの投げ方
まずFirestoreのエンドポイントなのですが
```https://firestore.googleapis.com/v1/projects/{PRJECT_NAME}/databases/(default)/documents/users/```
こちらになります。
usersというdocument(RDSでいうところのテーブルのようなもの？）を作成した前提となります。
`(default)`ってなんだ！？となりましたが、これは私にもよく分かりませんが、無視して良さそうです。

`{PROJECT_NAME}`は何の名前なのかというと、Firebase consoleを開いて

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/367893/a6b34a28-65b6-f5c7-b425-1a37f70577b2.png)
上記画像の「プロジェクトの設定」をタップすると載っています。（Firebase、色んなIDやら名前やらがあって何が何を指しているのか分かりにくいですよね）

それでは早速各メソッドの使い方について
#### GET
`https://firestore.googleapis.com/v1/projects/{PRJECT_NAME}/databases/(default)/documents/users/{USER_ID}`
これはそのままです。

#### POST
`https://firestore.googleapis.com/v1/projects/{PRJECT_NAME}/databases/(default)/documents/users?documentId={USER_ID}`
documentIdってなんだ？となりましたが、こういう風にIDを指定するものらしいです。ちなみにbodyも一癖あって
```json
{
    "fields": {
        "name": {
            "stringValue": "sample name"
        }
    }
}
```
fields!?どこからきた？！stringValue?!どこから？

```json
{
  "name": "sample name"
}
```

↑こういうのが良かったです！！（が、これではダメでした）

#### PATCH
`https://firestore.googleapis.com/v1/projects/{PRJECT_NAME}/databases/(default)/documents/users/{USER_ID}`
え、POSTはdocumentsでクエリ指定しなきゃいけなかったのにPATCHだとpath指定できるんだ・・・と思いましたが、とりあえずこれで良いみたいです。（テキトーにいじって見つけました）
bodyはPOSTと同じで
```json
{
    "fields": {
        "name": {
            "stringValue": "updated sample name"
        }
    }
}
```

#### DELETE
`https://firestore.googleapis.com/v1/projects/{PRJECT_NAME}/databases/(default)/documents/users/{USER_ID}`
bodyはnoneでDELETEできました。

続きまして、認証周りについてです


# 認証とセキュリティルールについて（Firebase authenticationを使っている場合）
Tokenを指定することによって、Firestore側で勝手にJWT認証をしてくれるようです。
例えば、```headers={"Authentication": "Bearer {ACCESS_TOKEN}}```とTokenを指定してリクエストを送ると、なんと・・・

```js
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    function isAuthenticated() {
      return request.auth != null;
    }
    (以下略)
```

上記はFirestoreのセキュリティルールを書く部分なのですが、なんとここの```request.auth```の部分によしなにJWT認証を済ませた後の認証情報が入っています。
公開鍵を使ってpayloadが改竄されていないか署名の検証を行ったりを自前で行うこともできますが、この辺りの検証をきちんとできずにそのままpayloadを復号して使ってしまう人がいたり（base64でencodeするだけで簡単に改竄できます）、headerを改竄して署名に使用されるアルゴリズムを空にして送ってくる攻撃の対策をし忘れていて検証を素通りしてしまうリスクがあったり、結構大変です。（実はFirestoreがここまで面倒を見てくれると知らずにCloud Functionsで検証の実装をやってしまった後に知りました・・・）

ちょっと話が逸れましたが、下記が簡単なセキュリティの設定方法です。


```js
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    function isAuthenticated() {
      return request.auth != null;
    }

    match /users/{userId} {
      // 自分自身しか登録できないようにする
      allow get, create, update, delete: if isAuthenticated() && request.auth.uid == userId;
  }
}
```

```match /users/{userId}```という部分は、```/users/{userId}```というpathにリクエストされた時に波括弧内を通るという意味です。
```allow get, create, update, delete```という部分は、get, create, update, deleteを許可するという意味です（readと書けばgetとlistを許可でき、writeと書けばcreateとupdateと、恐らくdeleteも許可されると思います）
ただし無条件で許可するわけではなく
```if isAuthenticated() && request.auth.uid == userId```という条件がありますね。
これはどういう意味かというと、```isAuthenticated()```については自分で定義したもので、request.authがnon nullであるかどうか見ています。署名の検証が成功したらここはnon nullになります。
そして、`request.auth.uid == userId`の部分についてですが、これは、`/users/{userId}`の`userId`と、tokenのpayloadを検証したときのuidが一致しているかという情報を見ています。
これで本人確認ができます。tokenは改竄されていないので、そのpayloadから取り出したuidが`request.auth.uid`であり、それと一致したuserをREAD/WRITEできるということです（仮に誰かのuidを知っていても、自分以外の見ず知らずのuserを勝手にcreateしたりupdate, deleteはできなくなります。）

ちなみに書き忘れていましたが、access tokenの取得方法は
`https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key={SERVER_KEY}`
上記のエンドポイントに
```json
{
    "email": "sample@gmail.com",
    "password": "samplepassword",
    "returnSecureToken": true
}
```
上記のようなbodyをセットしてPOSTすると取得できます。（このエンドポイントも地味に検索してヒットしにくいです・・・）



ざっくりとですが、こんな感じでエンドポイントのセキュリティを設定できます。


それでは！