SPAで認証実装が複雑化する理由とトークンリフレッシュ問題
SPAのログイン実装は、見た目以上に設計難易度が高いです。フォーム送信とCookieだけで成立していた従来のWebアプリと違い、SPAでは「ログイン状態をどこに保持するか」自体を設計しなければならないためです。
特に多くの開発者がつまずくのが、アクセストークンとリフレッシュトークンの扱いです。これは単なるライブラリ設定の話ではなく、ブラウザのセキュリティモデルに直接関係しています。
なぜSPAはセッション認証をそのまま使えないのか
従来のWebアプリでは、ログインするとセッションCookieが保存され、以降のリクエストには自動的にCookieが送信されます。開発者は認証を意識せずに画面を作れました。
SPAでは事情が変わります。SPAはAPIサーバと分離される構成が多く、次の形になります。
- フロントエンド(example.com)
- API(api.example.com)
このときブラウザはクロスオリジン通信として扱います。Cookieは自動送信されません。CORS設定やSameSite属性の影響を受けるため、従来のセッション認証が不安定になります。
そのためSPAではトークン認証が採用されることが増えました。
アクセストークンとは何か
ログイン成功時、サーバはユーザーを証明する短命のトークンを発行します。
{ "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." }
フロントエンドはAPI呼び出し時にヘッダへ付与します。
Authorization: Bearer eyJhbGciOi...
これによりサーバはユーザーを認識します。
ここまではシンプルですが、問題は有効期限です。
なぜトークンは短命にする必要があるのか
トークンは「持っている人が本人」とみなされます。つまり盗まれた場合、完全に乗っ取られます。Cookieならサーバ側で失効できますが、署名付きトークンは基本的に無効化が困難です。
そのためアクセストークンは数分〜数十分で期限切れにします。
ここで問題が発生します。
トークンリフレッシュ問題
期限が切れたトークンを使ってAPIを呼ぶと、401 Unauthorizedが返ります。
HTTP/1.1 401 Unauthorized
ユーザーはログアウトしたわけではありませんが、アプリは認証失敗として扱います。何もしないと、操作中に突然ログイン画面へ戻る挙動になります。
これを防ぐため、リフレッシュトークンを使います。
リフレッシュトークンの役割
リフレッシュトークンは長寿命の再発行用トークンです。アクセストークンが失効したとき、新しいトークンを取得します。
if (response.status === 401) { await refreshToken(); retryRequest(); }
一見簡単に見えますが、実装には複数の落とし穴があります。
同時リクエスト問題
SPAでは画面表示時に複数APIを同時に呼びます。トークン期限切れの場合、すべてのリクエストが同時に401になります。
結果として、同時に複数のリフレッシュ処理が走ります。
- トークン再発行が競合
- 古いトークンで再試行
- 無限リトライ
これが「ランダムにログアウトする」現象の原因になります。リフレッシュ処理は排他制御が必要です。
トークンの保存場所問題
次に重要なのが保存場所です。
候補は主に2つあります。
- localStorage
- Cookie
localStorageは扱いやすいですが、XSSに弱いです。スクリプトが実行されるとトークンを盗まれます。
一方、HttpOnly CookieはJavaScriptから読めないため安全ですが、CSRF対策が必要になります。どちらも完全ではありません。
よくある危険な実装
実務で多い失敗があります。
- アクセストークンをlocalStorage保存
- 有効期限を長期間に設定
- リフレッシュトークンも同じ場所に保存
これはセッション固定攻撃やXSSの被害を大きくします。攻撃者にとっては「ログイン状態を永続的に盗める」状態になります。
なぜSPAだけ問題になるのか
MPAではブラウザが自動的にCookieを送信し、セッション更新もサーバが行っていました。フロントエンドは認証の存在を意識しませんでした。
SPAでは、認証制御の一部をクライアントが担います。つまり「セキュリティの責任の一部がJavaScriptに移る」ことになります。これが難しさの本質です。
対策の考え方
実務では次の構成が比較的安定します。
- アクセストークン:短命
- リフレッシュトークン:HttpOnly Cookie
- 自動再取得:排他制御付き
これによりXSSとCSRFのリスクバランスを取れます。
まとめ
SPAで認証が複雑化する理由は、トークン技術が難しいからではありません。ブラウザが従来担っていたセッション管理を、アプリケーションが実装する必要があるためです。
トークンリフレッシュは「ログインを維持する仕組み」ではなく、「盗まれても被害を限定する仕組み」です。この視点で設計すると、保存場所や期限の意味が理解しやすくなります。
ログイン機構は後付けすると必ず破綻します。SPAでは画面設計と同時に認証フローを設計することが、最も重要な安定化ポイントになります。