Firebase Authentication で認証状態になったときだけ閲覧できるページを作る

認証状態のときのみ閲覧できるページを作りたかったが Fireabase Authentication に少し癖があったのでメモする。

開発環境

  • firebase: 9.1.3
  • react: 17.0.2

本文

Firebase authentication で認証する

まず Firebase authentication で認証するコードを実装する。

お手軽に FirabaseUI を使いたかったのだが、Firebase v9にまだ正式対応していなかったので自力実装した。
ただ結論から言えば自力実装でもかなり簡単だった。

まずFirebaseの初期化を行う。こんな感じ にした。

次に こんな感じ にログインページを簡素に作成した。
ログインしたら /input に飛ぶようにしている。
これだけで認証は完了する。

なお今回作っているのは要件としてログインするのは自分一人だけで、新規アカウント作成の必要は無いし、パスワードリセットも Firebase コンソールからやるので、ログイン以外は実装の必要が無い。

ルーティングのときに認証状態を確認する

前述の通り認証するのだが、認証状態の確認に癖がある。

現在ログインしているユーザーを取得する に書いてあるのだがわかりづらい。

要するに onAuthStateChanged の中でユーザーを取得しないと、認証済みのはずなのにユーザーが取得できないという事態が発生する。(もちろんそうしなくてもきちんとユーザーを取得できる実装はできるらしいが、onAuthStateChanged の中でユーザーを取得するのが早い)

ということで、下記が「ログインしていなかったら /signin に飛ばす」ルーティングを行うための実装である。

ReactでFirebase Authenticationを使う を大いに参考にさせていただいた。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import React, { useState, useEffect } from 'react';
import { Redirect } from 'react-router-dom';
import { app } from './firebase';
import { getAuth, onAuthStateChanged } from "firebase/auth";

function Auth(props) {
    // 一度の setXX で値を同時に更新しないと値の反映タイミングの問題で毎回 /signin に飛ばされるのでこうした
    const [signinCheck, setSigninCheck] = useState({ signinCheck: false, signedIn: false });

    useEffect(() => {
        const auth = getAuth();
        var unsubscribe = onAuthStateChanged(auth, (user) => {
            if (user) {
                setSigninCheck({ signinCheck: true, signedIn: true });
            } else {
                setSigninCheck({ signinCheck: true, signedIn: false });
            }
        })
        unsubscribe();
    }, []); // ここに空配列を渡さないとレンダリングの度に useEffect が呼ばれ続ける
    if (!signinCheck.signinCheck) {
        return (
            <div>Loading...</div>
        );
    }
    if (signinCheck.signedIn) {
        return props.children;
    } else {
        return <Redirect to="/signin" />
    }
}

export default Auth;

7 行目に書いてあるコメントの意味

当初は参考記事の内容をそのまま hooks 利用するように書き換えたので

const [signinCheck, setSigninCheck] = useState(false);
const [signedIn, setSignedIn] = useState(false);

// 中略

setSigninCheck(true);
setSignedIn(true);

というように書いていたのだが、これだと signinCheck と signedIn が同時に更新されず、

  • signinCheck = true
  • signedIn = false

という状態が一瞬生み出されてしまい、/signin に飛ばされてしまうという事象が発生した。

この事象自体は単純に

setSignedIn(true);
setSigninCheck(true);

と set を呼ぶ順番を逆にするだけでも回避可能だったのだが、書く順番に依存するようなコードは書きたくないので、二つの値を確実に同時に更新できるようにした。

認証状態のときだけ /input に行けるようにする

前述の実装を Auth という名前で呼んで下記のようにすると認証状態のときだけ /input に行けるようになる。

import React from "react";
import {
  BrowserRouter as Router,
  Switch,
  Route
} from "react-router-dom";
import List from './List';
import Input from './Input';
import Signin from './Signin';
import Auth from './Auth';

export default function App() {
  return (
    <Router>
      <Switch>
        <Route exact path="/" component={List} />
        <Route exact path="/signin" component={Signin} />
        <Auth>
          <Switch>
            <Route exact path="/input" component={Input} />
          </Switch>
        </Auth>
      </Switch>
    </Router>
  );
}

真面目にやるなら認証状態のときは /signin に行けないようにする実装も必要なのだが、今回は自分以外は認証を行わない想定なので無視することにする。

以上