LoginSignup
26
21

More than 3 years have passed since last update.

SPA(React) + Firebase Authentication でリロード時もログイン情報を維持できるようにする

Last updated at Posted at 2020-07-05

ちょっとハマったので備忘録
ここで紹介する方法以外でベストプラクティス等あればコメントで教えていただけると嬉しいです

Firebase Authentication + SPA でよくある設計

Firebase Authentication を使ったログイン機能付き SPA の実装例をググったときに見かける設計として

「認証が必要なページにアクセスされたときは、ログインしていればそのまま表示し、ログインしていなければログインページへ飛ばす」

というのがよくあるパターンだと思います。例えば React Router を使うと次のような実装が考えられます。 
※ React Router ドキュメントのコードをベースにしています https://reactrouter.com/web/example/auth-workflow


const PrivateRoute: React.FC<RouteProps> = ({ children, ...rest }) => {
  // getUser は保持されたユーザー情報を取得するための何らかの関数です。実装は省略
  // 後で登場する setUser 関数で保持したユーザー情報を取り出します
  // state を使ってもいいし、Redux 等のストアを使ってもいいし、Context を使っても構いません
  const user = getUser();

  return (
    <Route
      {...rest}
      render={({ location }) => {
        if (user) {
            return children;
        } else {
            return <Redirect to={{ pathname: 'login', state: { from: location } }} />;
        }
      }}
    />
  );
};

export default function App() {
  useEffect(() => {
    firebase.auth().onAuthStateChanged((user) => {
      if (user) {
        // setUser はユーザー情報を保持しておくための何らかの関数です。実装は省略
        // state を使ってもいいし、Redux 等のストアを使ってもいいし、Context を使っても構いません
        setUser(user);
      }
    });
  }, []);

  return (
    <>
      <Router>
        <Link to="/public">ログインが不要なページ</Link>
        <Link to="/private">ログインが必要なページ</Link>
        <Switch>
      <Route path="/login">
            <>
              <div>ログインページです</div>
              {/* Firebase Authentication へのログイン方法は何でも OK ですが */}
              {/* ここでは StyledFirebaseAuth を使ってログイン画面を表示します  */}
              <StyledFirebaseAuth ... />
            </>
          </Route>
          <Route path="/public">
            <div>このページは誰でも見ることができます</div>
          </Route>
          <PrivateRoute path="/private">
            <div>このページはログインしたユーザーだけが見ることができます</div>
          </PrivateRoute>
        </Switch>
      </Router>
    </>
  );
}

この実装で基本的には問題ないです。トップページ / から /public に遷移するとログインの有無に関係なくページが表示され、 /private に遷移するとログインしていれば表示されるし、ログインしていなければログインページへリダイレクトされます

問題点

ページ遷移でアプリケーション内をぐるぐるする分には問題ないのですが、 /private にいる状態でページをリロードしてみると先程ログインしたのにまたログインページへ飛ばされてしまいます
Firebase Authentication 的にはログイン状態ではあるのですが、 firebase.auth().onAuthStateChanged は非同期で走るので、 /private にダイレクトにアクセスすると setUser がされる前に getUser が実行されてしまいます
setUser されてないのでユーザー情報は存在せず、ログインされていない判定になりログインページへ再び飛ばされてしまいます

解決方法

実装を工夫してあげる必要があります
解決方法はシンプルで onAuthStateChanged が終わってからページを表示するかログインページへ飛ばすかどうかを確定させるアプローチで対処できます
(ここでは React Router を使った場合の解決方法を書いていますが、他のルーターの場合でもミドルウェアを使って似たようなことができると思います)

PrivateRoute を次のように書き換えてあげます


const PrivateRoute: React.FC<RouteProps> = ({ children, ...rest }) => {
  const [authChecked, setAuthChecked] = useState(false);

  useEffect(() => {
    firebaseAuth.onAuthStateChanged((user) => {
      if (user) {
        setUser(user);
      }

      setAuthChecked(true);
    });
  }, []);

  const user = getUser();

  return (
    <Route
      {...rest}
      render={({ location }) => {
        if (authChecked) {
          if (user) {
            return children;
          } else {
            return <Redirect to={{ pathname: 'login', state: { from: location } }} />;
          }
        } else {
          return <></>;
        }
      }}
    />
  );
};

「ログイン状態の確認が終わったか」「ログインしているかどうか」の2段階で分岐することで UX を損なうことなくリロード問題に対処できるようになりました

26
21
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
21