simplejwt の refresh token を扱うときに 500 を返してしまう典型パターンと、401 へ寄せる書き方の学習メモ。綴Photo では最初からこの形で書いている、という確認も兼ねている。
この記事でわかること
RefreshToken(raw)が無効入力でTokenErrorを投げる仕様- リフレッシュ系エンドポイントを 401 に寄せる例外ハンドリングのパターン
- 「同じ目的の view が複数ある」ときの粒度合わせの大事さ
概念
Django の rest_framework_simplejwt は、無効・期限切れ・改ざんされた refresh token を RefreshToken(raw) に渡すと TokenError(または派生クラス InvalidToken)を投げます。
JWT の基礎(構造・署名・なぜ「持ってる」だけでは認証にならないか)は、別記事 Next.js ミドルウェアで JWT 検証をするかしないかの設計判断 の Callout でまとめている。用語が初耳ならそちらを先に読むと話の前提が揃う。
やりがちな書き方(これが 500 になる)
Cookie から refresh token を取って新しい access token を返すエンドポイントで、RefreshToken(raw) を try/except なしで直接呼ぶ例。下は説明用の最小例。
# 500 になる書き方(説明用の最小例)
class TokenRefreshCookieView(APIView):
permission_classes = [AllowAny]
def post(self, request):
raw = request.COOKIES.get("refresh_token")
refresh = RefreshToken(raw) # ← raw が無効だと TokenError で 500
access = str(refresh.access_token)
return Response({"access": access})なぜ危険か
- 画面が無言で止まる: ユーザーには「なんかローディングが終わらない」体験しか見えません
- 監視ノイズ: 期限切れの正常フローですら 500 として積まれ、本物の 500 が見えなくなります
- セキュリティ調査の妨害: 改ざんされた refresh_token を投げるだけで 500 を誘発できるので、攻撃と混同しやすいです
401 に寄せる書き方
Cookie が空のケースも事前に弾きます。simplejwt を使うなら、refresh 系エンドポイントは最初からこの形で書いておくのが安全。
from rest_framework_simplejwt.exceptions import TokenError
class TokenRefreshCookieView(APIView):
permission_classes = [AllowAny]
def post(self, request):
raw = request.COOKIES.get("refresh_token")
if not raw:
return Response(
{"detail": "refresh token がありません。ログインしてください"},
status=401,
)
try:
refresh = RefreshToken(raw)
except TokenError:
return Response(
{"detail": "refresh token が無効です。再ログインしてください"},
status=401,
)
access = str(refresh.access_token)
return Response({"access": access})学びポイント
- 同じ目的の view が複数あるなら、例外ハンドリングの粒度を揃えます。token を扱う view が複数あって片方だけ try/except だと「こっちは 401、あっちは 500」が混在し、フロントのリカバリが片側で破綻します
- JWT 周りは「期限切れ」「署名不一致」「そもそも Cookie がない」の 3 ケースを必ず 401 に寄せます。500 は「想定外のバグ」だけのために取っておきます
- サーバーのステータスコードは、クライアントのリカバリを前提に設計します。サーバー単体で見てはダメ
おわりに
「ハッピーパス(=全部うまくいく前提の道。異常系・エラーケースの逆)だけ書いて、例外や境界が抜ける」のは外部入力を扱うコードの典型的な穴です。RefreshToken のように外部入力を直接渡すコードは、起きうる例外を一度公式ドキュメントで洗い出してから書く のが安全。綴Photo の refresh 系エンドポイントは最初からこの TokenError を捕まえる形で書いていて、今回は「なぜそう書くか」を改めて言語化した記録になります。
クライアントとサーバーは「ステータスコードという契約」で繋がっています。契約を書き換えるときは両側を同時に見ます。
この記事は2026年4月時点の自分の理解で書いています。
rest_framework_simplejwtの例外仕様はバージョンで挙動が変わることがあるので、最新の公式ドキュメントも合わせて参照してください。