React+Typescript+Firebase v9

## はじめにの前の結論

Firebase ver.9 を使って React で認証を実装した。

- ソースコード: [kurab/react-typescript-firebase](https://github.com/kurab/react-typescript-firebase)
- Firebase ver.8 vs ver.9 の薄い解説: [Firebase9 注入](#firebase9-注入)

## はじめに
紆余曲折あって、React の認証機能を Firebase に外出しした。ググれば余裕っしょと高を括っていたが、タイミング良く？悪く？Firebase の Node Module が 8 から 9 にバージョンアップした。ドキュメントを見ると、9はまだベータ版と書いてあるが、npm/yarn して入るのは、今日現在であれば、9.0.1 でそれが Latest となっているので、もう 9 なのだろう。[バージョンアップガイド](https://firebase.google.com/docs/web/modular-upgrade)を見ると、

> バージョン9の利点と限界
> 完全にモジュール化されたバージョン9には、以前のバージョンに比べて以下のような利点があります。
> 
> - バージョン9では、アプリのサイズを大幅に縮小することができます。最新のJavaScriptモジュール形式を採用しており、アプリに必要なアーティファクトだけをインポートする「ツリーシェイク」が可能です。アプリケーションにもよりますが、バージョン9でツリーシェイクを行うと、バージョン8で構築した同等のアプリケーションに比べて、80％のキロバイト削減が可能です。
> - バージョン9は今後も継続的な機能開発が行われますが、バージョン8は将来の時点で凍結されます。

とあるので、今後は 9 系統を使えば良かろうと判断した。が、いかんせん出たてホヤホヤなので、やってみたドキュメントがない。私のような日本語で教えて君にはつらいタイミングだ。8 の書き方だと、9 では動かない。面倒くさいなと思ったが、ベトナムは4連休だったので、トライしてみた。

## 作りたいもの
![Screen Shot 2021-09-06 at 2.08.41 PM.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245908/b3a0518f-f568-7db0-cfbe-fac8311c7411.png)
こんなようなものを作ることを目指した。割と汎用的な構成ではないかと思う。

最終的にできたものは、この図の Home にログアウトボタンを追加したのと、Facebook で登録ボタンは、Facebook でログインボタンと同じ機能になった。Firebase に Facebook で登録という機能はない（ログインすると登録される）。実際のアプリケーションでは、その他必要な情報を取得することもあるかと思うので、処理が分かれるかも知れないが、今回の例では必要ないので、省略した。

## バージョン情報

- React: 17.0.2
- Firebase: 9.0.1
- react-router-dom: 5.3.0

インストール手順は、こんな感じ

```
$ npx create-react-app app --template typescript
$ cd app
$ yarn add react-route-dom firebase
$ yarn add --dev @types/react-router-dom @types/firebase

$ npx browserslist@latest --update-db
```
最後のは、```yarn start``` したらやれと言われたのでやった。
リポジトリのルートディレクトリには、docker 関連等を置きたかったので、一段下げた。

## 実装方針

1. 素の Router を書き、各ページを作成・表示できるようにする
1. 各ページのデザイン（html+css）
1. Firebase を利用しない簡単な認証機能（メールとパスワードが一致するかだけ）の実装
1. 未認証の場合、ログイン画面にリダイレクトする PrivateRoute の実装
1. Firebase 注入
1. 認証後、リロード時にログイン画面に戻ってしまう件修正
1. Facebook ログインが Localhost に厳しい件の暫定対応

その他の方針としては、Type はきっちり明示、CSS FW は使わない、Atomic design の方向で、くらい。

出来上がったものは、こちら
▶ [kurab/react-typescript-firebase](https://github.com/kurab/react-typescript-firebase)

なお、エラーメッセージ表示などは、ほとんどやってない。alert が出るだけ。+ React.js は経験2〜3ヶ月のぺーペーなので、至らないところはゴメンナサイ。

## 実装

手順の5以外は、今回の本質ではないので、サラッと。

### 1. 素の Router を書き、各ページを作成・表示できるようにする
Routing の基本的な処理は、

```
app/src/router/Router.tsx
app/src/router/AuthRouter.tsx
```
と

```
app/src/App.tsx
```

で、```react-router-dom``` で実装している。完成形は、素ではなく、PrivateRoute や Provider などが入っている。
各ページは、

```
app/src/components/pages/*
```

### 2. 各ページのデザイン（html+css）

前述の ```app/src/components/pages/*``` に必要な要素を配置し、CSS は、```app/src/assets/styles/*``` にて scss で書いた。scss の build? compile? は VS Code 任せ。React っぽい書き方ではないと思うが、個人的にまだピンと来ていないというか、しっくり来るやり方が見つかっていないので、こんな感じで書いている。

実際の画面は、デザインに結構忠実なのでスクリーンショットは省略。

### 3. Firebase を利用しない簡単な認証機能（メールとパスワードが一致するかだけ）の実装

完成形では消えてしまっているが、

```
app/src/hooks/useAuth.ts
app/src/hooks/useLoginUser.ts
app/src/providers/LoginUserProvider.tsx
```

に書いてある（あった）。出来上がった Provider で Router の 404 ページ以外を囲っている。
```app/src/types/api/User.ts.bk``` は、この時点の名残り。なんか上がっちゃってた。```useAuth``` に user 配列をハードコードして if 文書いてただけ。

form の値の取得は、色々なやり方があると思うが、私は ```ChangeEvent<HTMLInputElement>``` で取得し、```useState``` を使って利用している。Register 画面のパスワード2回のチェックは、同じにならなければ、Register ボタンを押せないようにするという手抜き実装。細かくエラーメッセージ出したりする場合は、もうちょっと考える必要があるが、今回はこれで。

### 4. 未認証の場合、ログイン画面にリダイレクトする PrivateRoute の実装
PrivateRoute は、

```
app/src/router/PrivateRoute.tsx
```

もっとスマートな書き方もあるのかと思うけど、見た目の分かりやすさ優先。それを使って、 ```app/src/router/Router.tsx``` を書き換えた。

### 6. 認証後、リロード時にログイン画面に戻ってしまう件修正

```
app/src/providers/LoginUserProvider.tsx
```

の useEffect あたりが、それ。

### 7. Facebook ログインが Localhost に厳しい件の暫定対応

Facebook ログインは、Redirect URI が SSL でないと機能せず、Localhost に厳しい。Firebase チュートリアル記事で Facebook ログインが少ない理由は、これか…な？
という訳で、Docker を使って解決した。

> [Dockerでローカル開発環境のhttps化](https://qiita.com/muk-ai/items/413ae83b0a241495dd34)

この記事そのまま。Chrome のセキュリティを下げることになるが、この記事にある通り、Localhost なので、許容範囲かと思う。必要ないときは、元に戻しておけば良い。


## Firebase9 注入

では、いよいよ Firebase9。と言っても、ドキュメントに全部書いてあるので、それ読んでという感じ。

Firebase の設定、Facebook Dev の設定は、

- [【完全版】React の Firebase Authentication(認証)を基礎からマスターする](https://reffect.co.jp/react/react-firebase-auth)
- [【React/TypeScript】Firebase でメール認証と Google 認証を実装する](https://btj0.com/blog/react/firebase-auth/)
- [[Firebase] Authentication で Facebook 認証 (Web 編) etc.](https://blog.katsubemakito.net/firebase/firebase-authentication-facebook-web1)

このあたりを参照した。```.env``` には、Firebase の設定値を書く。

React 内の Firebase の実装は、```firebase.js/ts``` とかを作って…という解説が一般的だが、なんか嫌だったので、認証系処理を行う hook の ```app/src/hooks/useAuth.ts``` の中に書いた。

Firebase 8 系と 9 系の違いは、例えばメールとパスワードでユーザ登録する場合、8では、

```javascript:ver.8
firebase.auth().createUserWithEmailAndPassword(email, password)...
```

こんな感じになるが、9 の場合、

```javascript:ver.9
import { getAuth, createUserWithEmailAndPassword } from "firebase/auth";

const auth = getAuth();
createUserWithEmailAndPassword(auth, email, password)...
```

こんな感じになる。ドットチェインがなくなり、```auth = getAuth()``` して、パラメータとして渡すようになった。全ての関数を比較したわけではないが、概ねこんな感じ。

[WebとFirebaseの詳細](https://firebase.google.com/docs/web/learn-more#modular-version) にも説明がある。

> バージョン9のSDKの大部分は、バージョン8と同じパターンを踏襲していますが、コードの構成は異なります。一般的に、バージョン8は名前空間とサービスのパターンを指向しているのに対し、バージョン9は個別の機能を指向しています。例えば、バージョン8では firebaseApp.auth() のようなドットチェインがありましたが、バージョン9では firebaseApp を受け取り、Authentication インスタンスを返す getAuth() 関数に変更されています。


[JavaScript で Facebook ログインを使用して認証する](https://firebase.google.com/docs/auth/web/start#web-v8_1) にも、基本的な操作のいくつかの比較があるので参照。

その他のメソッドについては、[認証パッケージ](https://firebase.google.com/docs/reference/js/auth.md#auth_package) に丁寧に書いてあるので、それを見れば解決しないことはないと思われる。

```useAuth.ts``` 内で、```useLoginUser``` から ```setLoginUser``` を利用しているが、その実態は、```app/src/providers/LoginUserProvider.tsx``` で定義されている。

```LoginUserProvider.tsx
export type LoginUserContextType = {
  loginUser: LoginUser | null;
  setLoginUser: Dispatch<SetStateAction<LoginUser | null>>;
 };
...
  const [loginUser, setLoginUser] = useState<LoginUser | null>(null);
```

この部分。

ここで、Firebase でログインしたりした時の戻り値の型が必要になるが、API ドキュメントには、[UserCredential](https://firebase.google.com/docs/reference/js/auth.usercredential.md#usercredential_interface) が返ってくると約束（可愛い）。

Provider に渡すのは、UserCredential.user だが、型はここを見てもよく分からない。
ので、こんな感じにした。

```LoginUserProvider.tsx
import { UserCredential } from "firebase/auth";

type LoginUser = UserCredential["user"];
```

結局、これで良いのか良く分からないが、typescript が怒らないから（怒られた末こうなったから）、良いのかな？

なお、auth インスタンスは、```useAuth.ts``` の中で initialze した後に作っているが、これを export して使い回す必要はなく、必要な時に、```getAuth()``` すれば良い。





## おしまい

セキュアで便利な Auth Provider を自前で作るのは、ぶっちゃけ大変だ。途方に暮れるレベルで大変だ。NextAuth.js 以上のことはやりたくない。Firebase 落ちたらどうすんの？みたいな話は別途考える必要があるとして、Firebase や Auth0 のようなサービスがあるのはとても助かる。要件次第ではあるものの、使える時は使っていきたい。

公式ドキュメントが非常にしっかりしていたので、思ったより、Firebase の実装は簡単だった。今回の実装時間も Firebase 部分は全体の1割くらい。結局、認証機能の実装は、それ以外の部分が複雑で混ぜるとより分かりづらくなるので、関心事は分離して考えるのが良い。

今回のソースコードは、Firebase の設定などを済ませると、ログイン後にうちの可愛いネコが拝める特典付き。おわり。

![Screen Shot 2021-09-06 at 4.26.19 PM.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245908/0c0bcec6-5df7-59ca-ade6-14cb5415df30.png)