🙄

# 4.2 JWT 認証の仕組みと実装(Django REST Framework + Vue 編)

に公開

前回の記事ではセッション認証を使ったログインセッション管理を紹介した。
今回は外部公開 API や EC サイトでよく使われる JWT 認証 について整理する。


1. JWT とは何か

JWT(JSON Web Token)は、認証情報を JSON 形式でエンコードしたトークン。
以下の3部構成になっている。

  1. Header: アルゴリズムやタイプ
  2. Payload: ユーザー ID や有効期限
  3. Signature: 秘密鍵で署名

例:

xxxxx.yyyyy.zzzzz

特徴

  • サーバー側にセッションを保持しない → stateless 認証
  • トークンそのものにユーザー情報が含まれる
  • 有効期限を超えると無効になる

2. Django REST Framework での JWT 実装

DRF では djangorestframework-simplejwt を使うのが一般的。

インストール

pip install djangorestframework-simplejwt

設定

# settings.py
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": (
        "rest_framework_simplejwt.authentication.JWTAuthentication",
    )
}

URL ルーティング

# urls.py
from django.urls import path
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView

urlpatterns = [
    path("api/token/", TokenObtainPairView.as_view(), name="token_obtain_pair"),
    path("api/token/refresh/", TokenRefreshView.as_view(), name="token_refresh"),
]

レスポンス例(ログイン成功時)

{
  "access": "jwt_access_token_string",
  "refresh": "jwt_refresh_token_string"
}
  • access: 短期トークン(API 呼び出し用)
  • refresh: 長期トークン(access の再発行用)

3. Vue 側での利用方法

ログイン処理

import axios from "axios";

async function login(username, password) {
  const res = await axios.post("/api/token/", { username, password });
  localStorage.setItem("access_token", res.data.access);
  localStorage.setItem("refresh_token", res.data.refresh);
}

API リクエスト時のヘッダー付与

api.interceptors.request.use(config => {
  const token = localStorage.getItem("access_token");
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

トークンのリフレッシュ

api.interceptors.response.use(
  response => response,
  async error => {
    if (error.response.status === 401) {
      const refresh = localStorage.getItem("refresh_token");
      if (refresh) {
        const res = await axios.post("/api/token/refresh/", { refresh });
        localStorage.setItem("access_token", res.data.access);
        error.config.headers.Authorization = `Bearer ${res.data.access}`;
        return api.request(error.config);
      }
    }
    return Promise.reject(error);
  }
);

4. 実運用でハマるポイント:リフレッシュ競合問題

現象

  • 1画面で複数の API を同時に呼び出す
  • access トークンの期限が切れていると 同時に 401 エラーが返る
  • それぞれのリクエストが 個別に refresh API を叩いてしまう
  • refresh トークンは「一度使ったら無効」になる設定が多いため、競合で失敗する

結果 → 画面がエラーになり、ユーザーがログアウト状態になる


解決策 1: フロント側でリフレッシュ処理を直列化

リフレッシュ処理を「同時に 1回だけ」実行し、他のリクエストは待機させる。

👉 実装例は割愛(前章のサンプル参照)


解決策 2: refresh トークンを複数回使える設定にする

djangorestframework-simplejwt では以下を設定できる:

SIMPLE_JWT = {
    "ROTATE_REFRESH_TOKENS": False,
}

これにより同じ refresh トークンを複数回使えるため、並列リクエストでも失敗しない。
ただし「盗まれた refresh が無効化されにくい」ためセキュリティリスクが高まる。


5. セキュリティの考慮点

  • 保存場所

    • localStorage: XSS に弱い
    • Cookie + HttpOnly: セキュリティは高いが CSRF 対策が必要
  • 有効期限設計

    • access: 短め(数分〜数十分)
    • refresh: 長め(数日〜数週間)
  • ログアウト処理

    • refresh をブラックリスト化できるようにする

6. AI活用のポイント

実際の開発では、JWT 認証まわりは 「一見動くけど細部で不安定」 になりがち。

  • Vue 側で API をコールするタイミング(ページロード直後・ルーティング遷移後・F5 リロード後)によって挙動が変わる
  • リフレッシュ処理やログイン判定が「リンク遷移だと効かない」「F5押すと 401 で落ちる」といったケースが発生する
  • このあたりは AI が生成したコードをベースにすれば一度は通るが、実際のフローで微妙な動きを調整する必要がある

👉 AI の使い方としては、

  1. まず AI にインターセプターやリフレッシュ処理の雛形を出させる
  2. それをローカルで試し、F5 や別リンク遷移など「実利用の動線」でテストする
  3. 挙動が不安定なら、ログを出しながら逐次修正

この反復で仕上げるのが現実的。
AI のコード生成は「初期セットアップの高速化」には最適だが、最終的な安定動作は人間の検証が必須になる。


7. セッション認証と JWT 認証の使い分け

項目 セッション認証 JWT 認証
管理方式 サーバーがセッションを保持 stateless(トークンのみ)
CSRF 対策 必要 基本不要
有効期限 セッションタイムアウト トークンの exp
スケーラビリティ セッション共有が必要 スケールアウトしやすい
向いているケース 社内業務システム EC サイト、スマホアプリ、外部 API

まとめ

  • JWT は stateless な認証方式 で外部公開 API に向いている
  • djangorestframework-simplejwt で簡単に導入可能
  • Vue 側では access/refresh を組み合わせて扱う
  • 複数 API を同時に叩いたときのリフレッシュ競合問題 に注意
  • 実運用では リロードや遷移時の微妙な挙動 が起きやすく、AI のコードは叩き台にして「人間の検証で仕上げる」ことが重要
  • プロジェクトでは 業務システムはセッション認証、EC サイトは JWT 認証 と使い分けている

次回は、部署・権限ごとのメニュー制御 を取り上げる予定。

Discussion