前回に続きReact + Firebaseのミニアプリを作っていきます。
前回はログイン処理の実装途中でした。
React + Firebaseのミニアプリを作る【認証編2】
前回に続きReact + Firebaseのミニアプリを作っていきます。 前回はユーザ登録を実装しました。 今回はログイン機能を作っていきます。 Contentsログイン処 ...
続きを見る
今回も引き続きログインを実装していきます。
Contents
ログイン処理の全体像(再掲)
以下がログイン処理の全体像です。1~2は前回対応しました。
- 登録したメールアドレス・パスワードで認証(ログイン)できる
- ログインしていなければログイン画面へリダイレクトさせる
- ログインしていればユーザ情報を使ってデータを取得・保存する
- リロード後もトークンがあればログイン状態を保つ
今回は3~4 + ログアウトを実装します。
ログインしていればユーザ情報を使ってデータを取得・保存する
ログイン状態でCRUDが動作するように変更していきます。
リクエストに認証情報を加える
まずはCRUDのリクエストに認証情報を加えて動作するようにします。
axios.jsを編集し、interceptorsを用いてリクエストに"?auth=トークン"
を付与します。
interceptorを使うことでリクエストを送る前や、レスポンスを受け取った後に追加の処理やエラーハンドリングを行うことが出来ます。
今回のようにCRUDのリクエスト全てに共通の処理(認証情報の付与)をさせたいときには便利です。
補足
ついでに今まで使っていたaxios.jsファイルは前回作成したaxiosフォルダに移動しておきましょう。
import { getItem } from "../utils/tokenUtils"; // リクエストのインターセプター instance.interceptors.request.use( config => { // リクエストが送られる「前に」実行される // 例えば、トークンを設定する config.url += "?auth=" + getItem("token"); return config; }, error => { // then/catchの処理の「前に」実行されるエラーハンドリング // console.log("=== Request Failed ==="); return Promise.reject(error); } );
これで認証情報を加えたので動作はするようになりました。ただし今の状態だと全てのユーザでデータが共有されてしまいますので、ユーザごとにデータを分けるように変更します。
CRUDをユーザIDを使って書き直す
【実装編2】で決めたデータ構造の通りuserId以下の階層にsubjectデータとQ&Aデータを格納します。したがって、例えばcreateSubjectの通信は以下のようになります。
const userId = getUserId(); const result = await axios.post(`/${userId}/subject.json`, subject);
Q&Aの方も含めて全てのリクエストで以下のように`/${userId}/`を追加します。
const userId = getUserId(); await axios.patch(`/${userId}/qa.json`, { [id]: qas });
一旦保存してある全てのデータを削除した上でもう一度CRUDを確認してみて下さい。
このように意図したデータ構造になっていることが確認できます。
リロード後もトークンがあればログイン状態を保つ
ここまででもう殆ど完成していますが、最後にリロード後にログイン状態を保つように変更します。
ページがレンダーされた時にトークンを所持しており、そのトークンの有効期限が切れていないかを確認します。
これをそのまま実装してみます。actions > auth.jsに以下のメソッドを追加します。
/** * トークンが存在するか、もしくはトークンの期限切れを確認 */ export const checkToken = () => dispatch => { const token = getItem("token"); if (!token) { // トークンが存在しなければサインアウトさせる dispatch(signout()); } else if (isExpired()) { // 「トークンが存在するが、期限切れ」の場合はメッセージを表示し、サインアウトさせる dispatch(signout()); dispatch( showMessage( "認証の期限が切れました。もう一度ログインして下さい。", "error" ) ); } else { // トークンが存在し、期限切れでない場合はログイン状態にする dispatch(signinSuccess()); } };
これをContent.jsがレンダーされたときにdispatchさせます。
components > layout > Content.jsに以下のコードを追加して下さい。
import { checkToken } from "../../actions/auth"; const dispatch = useDispatch(); useEffect(() => { dispatch(checkToken()); }, [dispatch]);
これでトークンを持っていれば自動でログインされるようになりました。
ログアウト(Sign Out)
最後にログアウト(Sign Out)のボタンを配置しましょう。
components > layout > Header.jsのExportボタンを削除し、ログアウトボタンを配置しましょう。
{isAuth ? ( <Button color="inherit" component={Link} to="/signout"> Logout </Button> ) : null}
ログイン状態のときだけ表示させるようにしています。
ログアウトはいくつか実装方法があります。
最も簡単なのは、ボタンのonClickにログアウト処理をdispatchするメソッドを割り当てることです。
他にも方法があり、今回はログアウトを行うSign Outコンポーネントを作成する方法で実装してみます。
components > content > auth > SignOut.jsを作成し、以下のようにします。
import React, { useEffect } from "react"; import { useDispatch } from "react-redux"; import { Redirect } from "react-router-dom"; import { signout } from "../../../actions/auth"; import { showMessage } from "../../../actions/uiState"; const SignOut = () => { const dispatch = useDispatch(); useEffect(() => { dispatch(signout()); dispatch(showMessage("サインアウトしました。", "success")); }, [dispatch]); return <Redirect to="/signin" />; }; export default SignOut;
レンダーされたときにsignout処理をdispatchし、かつ"/signin"にRedirectします。
Content.jsにRouteを追加することも忘れずに。
<Route path="/signout"> <SignOut /> </Route>
これでSign Outが出来ました。
完成
これでユーザ登録・ログイン・ログアウト、また認証情報込みでCRUDが出来るようになりました。
ログイン時のBackdropの表示や、コード分割など所々不備がありますが、完成コードでは修正していますので気になる方はそちらをチェックして下さい。
完成コードはこちらからダウンロード↓
今回は実装しませんでしたが、Firebase Auth REST APIではRefresh Tokenを使ったトークンの再取得やメールアドレスの変更・パスワードリセットなど多くのことができますのでチェックしてみるといいでしょう。
ただしFirebaseを使う場合はSDKを使うことの方が多いと思いますが......