はじめにの前の結論
Firebase ver.9 を使って React で認証を実装した。
- ソースコード: kurab/react-typescript-firebase
- Firebase ver.8 vs ver.9 の薄い解説: Firebase9 注入
はじめに
紆余曲折あって、React の認証機能を Firebase に外出しした。ググれば余裕っしょと高を括っていたが、タイミング良く?悪く?Firebase の Node Module が 8 から 9 にバージョンアップした。ドキュメントを見ると、9はまだベータ版と書いてあるが、npm/yarn して入るのは、今日現在であれば、9.0.1 でそれが Latest となっているので、もう 9 なのだろう。バージョンアップガイドを見ると、
バージョン9の利点と限界
完全にモジュール化されたバージョン9には、以前のバージョンに比べて以下のような利点があります。
- バージョン9では、アプリのサイズを大幅に縮小することができます。最新のJavaScriptモジュール形式を採用しており、アプリに必要なアーティファクトだけをインポートする「ツリーシェイク」が可能です。アプリケーションにもよりますが、バージョン9でツリーシェイクを行うと、バージョン8で構築した同等のアプリケーションに比べて、80%のキロバイト削減が可能です。
- バージョン9は今後も継続的な機能開発が行われますが、バージョン8は将来の時点で凍結されます。
とあるので、今後は 9 系統を使えば良かろうと判断した。が、いかんせん出たてホヤホヤなので、やってみたドキュメントがない。私のような日本語で教えて君にはつらいタイミングだ。8 の書き方だと、9 では動かない。面倒くさいなと思ったが、ベトナムは4連休だったので、トライしてみた。
作りたいもの
こんなようなものを作ることを目指した。割と汎用的な構成ではないかと思う。
最終的にできたものは、この図の 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 関連等を置きたかったので、一段下げた。
実装方針
- 素の Router を書き、各ページを作成・表示できるようにする
- 各ページのデザイン(html+css)
- Firebase を利用しない簡単な認証機能(メールとパスワードが一致するかだけ)の実装
- 未認証の場合、ログイン画面にリダイレクトする PrivateRoute の実装
- Firebase 注入
- 認証後、リロード時にログイン画面に戻ってしまう件修正
- Facebook ログインが Localhost に厳しい件の暫定対応
その他の方針としては、Type はきっちり明示、CSS FW は使わない、Atomic design の方向で、くらい。
出来上がったものは、こちら
▶ 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 を使って解決した。
この記事そのまま。Chrome のセキュリティを下げることになるが、この記事にある通り、Localhost なので、許容範囲かと思う。必要ないときは、元に戻しておけば良い。
Firebase9 注入
では、いよいよ Firebase9。と言っても、ドキュメントに全部書いてあるので、それ読んでという感じ。
Firebase の設定、Facebook Dev の設定は、
- 【完全版】React の Firebase Authentication(認証)を基礎からマスターする
- 【React/TypeScript】Firebase でメール認証と Google 認証を実装する
- [Firebase] Authentication で Facebook 認証 (Web 編) etc.
このあたりを参照した。.env
には、Firebase の設定値を書く。
React 内の Firebase の実装は、firebase.js/ts
とかを作って…という解説が一般的だが、なんか嫌だったので、認証系処理を行う hook の app/src/hooks/useAuth.ts
の中に書いた。
Firebase 8 系と 9 系の違いは、例えばメールとパスワードでユーザ登録する場合、8では、
firebase.auth().createUserWithEmailAndPassword(email, password)...
こんな感じになるが、9 の場合、
import { getAuth, createUserWithEmailAndPassword } from "firebase/auth";
const auth = getAuth();
createUserWithEmailAndPassword(auth, email, password)...
こんな感じになる。ドットチェインがなくなり、auth = getAuth()
して、パラメータとして渡すようになった。全ての関数を比較したわけではないが、概ねこんな感じ。
WebとFirebaseの詳細 にも説明がある。
バージョン9のSDKの大部分は、バージョン8と同じパターンを踏襲していますが、コードの構成は異なります。一般的に、バージョン8は名前空間とサービスのパターンを指向しているのに対し、バージョン9は個別の機能を指向しています。例えば、バージョン8では firebaseApp.auth() のようなドットチェインがありましたが、バージョン9では firebaseApp を受け取り、Authentication インスタンスを返す getAuth() 関数に変更されています。
JavaScript で Facebook ログインを使用して認証する にも、基本的な操作のいくつかの比較があるので参照。
その他のメソッドについては、認証パッケージ に丁寧に書いてあるので、それを見れば解決しないことはないと思われる。
useAuth.ts
内で、useLoginUser
から setLoginUser
を利用しているが、その実態は、app/src/providers/LoginUserProvider.tsx
で定義されている。
export type LoginUserContextType = {
loginUser: LoginUser | null;
setLoginUser: Dispatch<SetStateAction<LoginUser | null>>;
};
...
const [loginUser, setLoginUser] = useState<LoginUser | null>(null);
この部分。
ここで、Firebase でログインしたりした時の戻り値の型が必要になるが、API ドキュメントには、UserCredential が返ってくると約束(可愛い)。
Provider に渡すのは、UserCredential.user だが、型はここを見てもよく分からない。
ので、こんな感じにした。
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 の設定などを済ませると、ログイン後にうちの可愛いネコが拝める特典付き。おわり。