10分で理解するJWT:設計と落とし穴

ぜ、あなたの認証システムはスケールしないのでしょうか?サーバーのメモリがセッション情報で溢れかえり、DBへの問い合わせがボトルネックになっているなら、それは「ステートフル」な設計の限界かもしれません。現代の分散システムにおいて、JWT(JSON Web Token)はもはや選択肢の一つではなく、必須の知識となりました。

しかし、警告しておきます。JWTは魔法の杖ではありません。誤った実装は、従来のセッション管理よりも深刻な脆弱性を生み出します。本記事では、JWTのアーキテクチャを解剖し、シニアエンジニアが知っておくべき「戦略的活用法」と「回避すべき落とし穴」を徹底的に解説します。

JWTの解剖学:3つのパーツが語る真実

JWTの実体は、.(ドット)で区切られた3つのBase64Urlエンコード文字列です。これらが結合して、xxxxx.yyyyy.zzzzzという形式になります。それぞれの役割を正しく理解することが、セキュリティ設計の第一歩です。

1. ヘッダー (Header):暗号化方式の宣言

メタデータを格納します。ここで最も重要なのはalg(アルゴリズム)です。


{
  "alg": "HS256", // 共通鍵方式
  "typ": "JWT"
}
Pro Tip: アルゴリズムの選択基準
単一のモノリス構成なら高速なHS256 (HMAC)で十分です。しかし、マイクロサービスで認証サーバーとリソースサーバーが分かれている場合は、秘密鍵を共有しなくて済むRS256 (RSA公開鍵方式)を選択するのが鉄則です。

2. ペイロード (Payload):運びたいデータ

ここに「クレーム(Claim)」と呼ばれる実データを格納します。ユーザーIDや権限情報などです。

  • Registered Claims: iss (発行者), exp (有効期限), sub (ユーザーID) など、規格で予約されたキー。
  • Private Claims: アプリケーション独自のデータ(例: role: admin)。
注意:機密情報は厳禁
ペイロードは単にBase64エンコードされているだけで、暗号化されていません。デコードすれば誰でも中身が見えます。パスワードや個人情報(PII)をここに入れるのは自殺行為です。

3. 署名 (Signature):改ざん防止の要

ヘッダーとペイロード、そしてサーバーだけが知る「秘密鍵」を使って生成されたハッシュ値です。


// 署名生成のイメージ
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret_key
)

サーバーはこの署名を再計算して照合することで、「トークンが改ざんされていないか」「信頼できるサーバーが発行したか」を瞬時に検証します。DBへの問い合わせは一切不要です。

なぜJWTなのか?ステートレスがもたらす革命

従来のセッションID方式では、サーバーはメモリやDBにセッション情報を保持する必要がありました(ステートフル)。JWTはこの前提を覆します。

特徴 セッション認証 JWT認証
状態保持 サーバー側 (Redis/DB) クライアント側 (トークン内)
スケーラビリティ 低い (DB負荷増) 高い (検証のみでOK)
クロスドメイン CORS/Cookie制約あり 容易 (HTTP Header)

特にマイクロサービスアーキテクチャにおいて、各サービスが独立してトークンを検証できる点は強力です。認証サーバーへの問い合わせトラフィックを削減し、システムの疎結合を維持できます。

【最重要】JWTの致命的な弱点と対策

ここからが本題です。JWTには「一度発行したら、有効期限が切れるまでサーバー側から無効化できない」という特性があります。これがセキュリティ上の最大の懸念点です。

シナリオ:トークンが盗まれたら?

もし攻撃者が有効期限の長いJWTを盗んだ場合、彼らはその期限が切れるまでユーザーになりすまし続けることができます。パスワードを変更しても、すでに発行されたJWTは有効なままです。

解決策:Refresh Token パターン

この問題を解決するための業界標準パターンを紹介します。

  1. Access Token (短命): 有効期限を15分〜1時間に設定。APIアクセスに使用。
  2. Refresh Token (長命): 有効期限を数日〜数週間に設定。新しいAccess Tokenの再発行に使用。

Access Tokenが漏洩しても被害は短時間で済みます。また、ユーザーを強制ログアウトさせたい場合は、サーバー側のDBにあるRefresh Tokenを無効化(削除)すれば、次の更新タイミングでアクセスを遮断できます。

やってはいけないこと:
Access Tokenの有効期限を長く設定すること(例:1週間)。利便性は上がりますが、セキュリティリスクは許容できないレベルまで跳ね上がります。

実装コード:Pythonでの検証ロジック

実際にバックエンドでJWTを処理する際のイメージです。ライブラリを使用することで、署名の検証や有効期限のチェックを自動化できます。


import jwt
import datetime

SECRET_KEY = "super-secret-key" # 実際は環境変数で管理すること

def create_token(user_id):
    payload = {
        "sub": user_id,
        "iat": datetime.datetime.utcnow(),
        "exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=30) # 30分で失効
    }
    # アルゴリズムを指定してエンコード
    token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
    return token

def verify_token(token):
    try:
        # 署名の検証と有効期限(exp)のチェックを同時に実行
        payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
        return payload["sub"]
    except jwt.ExpiredSignatureError:
        return "Error: トークンの有効期限が切れています"
    except jwt.InvalidTokenError:
        return "Error: 不正なトークンです"

どこに保存すべきか? XSS vs CSRF

フロントエンドエンジニアを悩ませる永遠の課題、「LocalStorageかCookieか」について決着をつけましょう。

  • LocalStorage: 実装は簡単ですが、XSS(クロスサイトスクリプティング)脆弱性がある場合、JavaScriptからトークンを盗み出されるリスクがあります。
  • HttpOnly Cookie: JavaScriptからアクセスできないため、XSS攻撃に対して堅牢です。ただし、CSRF(クロスサイトリクエストフォージェリ)対策が別途必要になります。
推奨構成:
セキュリティを最優先するなら、HttpOnly Cookie + SameSite=Strict 属性での運用を強く推奨します。モバイルアプリの場合はセキュアストレージを利用してください。

結論:JWTはツールであり、戦略である

JWTはスケーラビリティと効率性をもたらす強力な技術ですが、導入には「ステートレスであること」の代償(失効の難しさ)を理解する必要があります。

あなたのプロジェクトでJWTを採用する際は、以下のチェックリストを確認してください。

  • 通信はすべてHTTPSで行われているか?
  • 署名アルゴリズムは適切か(HS256 vs RS256)?
  • Access Tokenの寿命は適切に短いか?
  • Refresh Tokenのローテーション戦略はあるか?

これらをクリアして初めて、JWTはあなたのシステムの堅牢な基盤となります。流行に流されず、アーキテクチャの要件に合わせた適切な認証方式を選択してください。

Post a Comment