1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Reactで認証Tokenを保存するMyBestPractice

Last updated at Posted at 2025-12-17

概要

Reactでの認証は、やらないといけないけど、面倒な実装の一つだと思います。

いろんなやり方があるかと思いますが、自分なりに見つけた、Reactでの認証トークンの取り扱いを記載していきたいと思います。

ここの記事では、RESTfulなAPIサーバーにアクセスする前提で、認証にJWT等のトークン情報を用いる場合を対象とします。

この記事の対象は、

  • クライアント側でトークン情報の保存
  • APIリクエスト時のトークン情報の読み出し
  • 認証されたユーザーが見られるページの制御

注意:記載したものはセキュリティ的な懸念が出てきたら随時更新したいと思います。

使うライブラリ

  • React
  • React Router

認証の流れ

auth.png

クライアント側でのトークン情報の保存

ユーザーがログイン操作をしたら、サーバーからはJWTが返ってきます。

それをどこに保存すべきか、選択肢は

  • Local Storage
  • Session Storage
  • Cookie

今回は、Javascript・Typescriptで簡単に操作が可能で、タブが閉じるまで生き続けるSessionStorageを保存先とします。
なお、LocalStorageの場合もあまり違いはありません。

また、前提として、複数のタブで同じサービスを開いてログイン操作するなどはしないものとします。
(その場合の実装も後ほど書いてみたいと思います。)

方針:Reactカスタムフックの利用

React カスタムフックでSession Storageの情報を監視・操作します(リアルタイム監視ではないです)

このフックはこちらの記事を参考にさせていただきました。

https://qiita.com/ShimeiYago/items/28f6d088704bf6accf02
const STORAGE_EVENT_KEY = 'authchanged';

lib/useAuthStorage.tsx
import { useCallback, useEffect, useState } from 'react';

export default function useAuthStorage() {
  const getStorage = () => {
    if (typeof window === 'undefined') return null;
    return sessionStorage.getItem('token');
  };

  const [token, setTokenState] = useState(getStorage);

  useEffect(() => {
    const handleStorageChange = () => {
      setTokenState(getStorage());
    };

    window.addEventListener(STORAGE_EVENT_KEY, handleStorageChange);

    return () => {
      window.removeEventListener(STORAGE_EVENT_KEY, handleStorageChange);
    };
  }, []);

  const setToken = useCallback((newToken: string | null) => {
    if (typeof window === 'undefined') return;

    if (newToken) {
      sessionStorage.setItem('token', newToken);
    } else {
      sessionStorage.removeItem('token');
    }

    setTokenState(newToken);
    window.dispatchEvent(new Event(STORAGE_EVENT_KEY));
  }, []);

  return { token, setToken };
}

これを使って、

  • 認証情報が必要なページでtoken情報をuseAuthStorage()から取り出したり、

  • サインイン時にsetTokenを使って、新しいトークン情報を保存したりできます。

例えば、サインインページでは次のように使ったりします。

Signin.tsx
export default function Signin() {
  const { token, setToken } = useAuthStorage(); // <--- 設定されているトークンと設定用Functionを取得

  const handleSubmit = useCallback(
    (values: SigninForm) => {
      fetch('/v1/api/auth/signin', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          name: values.username,
          password: values.password,
        }),
      })
        .then((raw) => raw.json())
        .then((d) => setToken(d.token)) // <--- setTokenで新しいトークンを保存

useAuthStorage()をコンポーネントで呼び出せば、設定されているトークンを取得できるので、それを使ってAPIアクセスも可能です。

Sample.tsx
export default function Signin() {
  const { token } = useAuthStorage(); // <--- 設定されているトークンを取得

  useEffect(() => {
      fetch('/v1/api/sample', {
        headers: { 
            'Content-Type': 'application/json', 
            Authorization: `Bearer ${token}` 
        }
      });
  }, [])

サインアウトをしたい場合は、次のページを開くことできるようにしました。
ReactRouterも併用して、サインアウト後はサインインページに飛ばすようにしています。

Singout.tsx
export default function Signout() {
  const { setToken } = useAuthStorage();

  useEffect(() => {
    setToken(null);
  }, [setToken]);

  return <Navigate to="/auth/signin" />;
}

認証されたユーザーが見られるページの制御

認証していないとデータが取得できず、ページがうまく表示されないという状況はあるあるだと思います。

認証していないユーザーがアクセスしたら、サインインページへ飛ばすような流れをReact Routerを使って実装します。

流れ

  • 認証されているかを確認するガードコンポーネントの作成
  • Routesへの適用

ガードコンポーネントで認証トークンの有無を確認

ガードコンポーネントの役割は、

  • 認証トークンがあるかを確認
  • トークンがあれば、Outletを利用して、子のRouteコンポーネントを有効化
  • トークンがなければ、Navigateを利用して、Sigininページへ飛ばす
AuthedRoutes.tsx
export default function AuthedRoutes() {
  const { token } = useAuthStorage();
  return token ? <Outlet /> : <Navigate to="/auth/signin" replace />;
}

ガードコンポーネントをRouteに反映

認証が必要なパス(ページ)はAuthedRoutesで保護します。

App.tsx
export default function App() {
    return (
    //...省略
        <BrowserRouter>
          <Routes>
            <Route path="/auth/signup" element={<Signup />} />
            <Route path="/auth/signin" element={<Signin />} />
            <Route element={<AuthedRoutes />}>   
              {/* <------------ ここから下は認証トークンが必要(サインインしている必要あり)*/}
              <Route path="/" element={<PostNote />} />
              <Route path="/post" element={<PostNote />} />
              <Route path="/questions" element={<PostQuestions />} />
              <Route path="/diaries" element={<Diaries />} />
              <Route path="/auth/signout" element={<Signout />} />
            </Route>
          </Routes>
        </BrowserRouter>
    //...省略
}

まとめ

トークンは迷わず、ReactカスタムフックでSessionStorageを監視・操作して、ReactRouterで制御する。
一例として参考にしていただけたら幸いです!

関連プロジェクト

1
2
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
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?