JWT認証、アクセストークンとリフレッシュトークンで最低限決めておくこと

  • JWT
  • Auth
  • Security

この記事は、構成と文章の整理にAIを使っています。
あくまで読みやすくするための補助で、内容そのものをAIにすべて任せて書いているわけではありませんが、ところどころ相談しながら整えています。

JWTでアクセストークン+リフレッシュトークンの構成を組むとき、どこを決めればいいのかを一度整理した。

全体の構成

  • アクセストークン:JWT、短命(15〜30分程度)。サーバは署名と exp を見るだけでログイン状態を判定できる
  • リフレッシュトークン:アクセストークン切れ後の再発行用。長命(30日など)
  • クライアントは401を受けたらリフレッシュトークンで新しいアクセストークンを取りに行く
sequenceDiagram
    participant Client
    participant API
    Client->>API: リクエスト + アクセストークン
    API-->>Client: 401(トークン期限切れ)
    Client->>API: リフレッシュトークンで /refresh
    API-->>Client: 新アクセストークン(+新リフレッシュトークン)

ここまでは定番。この先の「どこまで作り込むか」で判断が分かれる。

1. 有効期限をどこに置くか

  • アクセストークンは短いほど漏洩時の被害が小さいが、リフレッシュの頻度が上がる
  • リフレッシュトークンは長いほどユーザーの再ログイン頻度は減るが、漏洩時の窓が広がる
  • 「絶対期限(発行から何日まで)」と「スライディング(使い続ける限り延長)」のどちらにするかは別軸。スライディングはUXがいいが、漏洩したトークンも同じ理屈で延命され続ける点に注意。

2. ローテーションをやるか

リフレッシュのたびに新しいリフレッシュトークンを発行して、古い方を即無効化する方式。これを入れないと、漏洩したリフレッシュトークンは期限が切れるまでずっと使われ続ける。

入れる場合は、旧トークンが再利用されたとき(=無効化したはずのトークンで再度リフレッシュ要求が来たとき)の扱いも決めておく。漏洩の証拠とみなして関連トークンを無効化する、いわゆる reuse detection。

3. 管理単位をユーザーにするか端末にするか

ここでハマりやすい。リフレッシュトークンを「ユーザーに1本」で管理すると、複数端末ログイン(スマホとタブレットなど)で片方をリフレッシュしたときに、もう片方のトークンまで巻き込んで無効化してしまう。

端末(セッション)単位で持たせれば解決する。

user_sessions
sessionId, userId, deviceInfo, createdAt, lastSeenAt, revokedAt
refresh_tokens
tokenId, sessionId, tokenHash, expiresAt, revokedAt, replacedByTokenId
  • ローテーションで無効化する範囲は「そのセッションの直前のトークンだけ」
  • reuse detection で無効化する範囲も「そのセッション配下だけ」(ユーザー全体に広げるかは別のポリシー判断)
  • 端末からはリフレッシュトークンと一緒に sessionId も送らせると、サーバ側の判定が単純になる

4. 同時リフレッシュの競合をどう抑えるか

端末内で複数リクエストがほぼ同時に401を受けると、リフレッシュが多重に走ることがある。片方がローテーションで新トークンを出した直後に、もう片方が旧トークンでリフレッシュしようとして失敗する。

クライアント側でリフレッシュ処理を single-flight にして、401を受けたリクエストはキューしておき、リフレッシュ成功後にまとめて再送するのが素直な対策。

5. 保存場所をどこにするか

  • ネイティブアプリならKeychain(iOS)/Keystore(Android)
  • Webならリフレッシュトークンを localStorage に置くとXSSでそのまま持っていかれる。HttpOnly Cookieに置く場合はCSRF対策(SameSite、CSRFトークンなど)とセット

6. 状態を持つかどうかのトレードオフ

ローテーション後の旧トークン無効化も、reuse detectionも、「どのトークンが最新か」を覚えておく必要がある処理なので、サーバが完全に状態を持たない構成とは原理的に相性が悪い。

状態を持たない方向に振る場合、選択肢はこのあたり:

  • リフレッシュトークンもJWTにして exp だけで判定する(強制ログアウトもreuse detectionも諦める前提を受け入れる)
  • 有効期限を短めにする、端末側の保管を徹底するなど予防策で漏洩時の被害を抑える
  • 「失効管理はしたいが自前DBは避けたい」なら、結局どこかにKVS相当の状態は必要になる

運用を軽くしたいか、漏洩時に止血したいかのトレードオフで、要件次第でどちらの構成もあり得る。