【実務・中級編】 OpenID Connect (OIDC) におけるIDトークンの検証手順 – 暗号理論・認証基盤 & エンドポイントセキュリティ防御ガイド

現場で汗を流すエンジニア諸君、お疲れ様。

今日は、多くの開発者が「なんとなく」実装し、そして多くのインシデントの温床となっている「OpenID Connect (OIDC) のIDトークン検証」について、裏側の泥臭い現実を話そう。

「JWTのライブラリを使っているから大丈夫」なんて思っていたら、明日にでも君のバックエンドが踏み台にされるかもしれない。なぜなら、ライブラリのメソッドを呼ぶだけでは、セキュリティの半分も担保できていないからだ。

1. なぜ「署名検証」だけでは足りないのか

JWT(JSON Web Token)を扱う際、多くのエンジニアが iss(発行者)や aud(対象者)のチェックを怠る。しかし、最も恐ろしいのは「アルゴリズムの強制」を忘れることだ。

攻撃者は、公開鍵で検証すべき署名を、なんと「対称鍵(HS256)」として解釈させようとする。具体的には、JWTのヘッダーにある alg を none に書き換えたり、公開鍵のビット列をHMACの秘密鍵として流用させたりする攻撃だ。これが成功すれば、君のシステムは攻撃者が発行した偽のIDトークンを「本物」と誤認し、特権ユーザーとしてログインを許してしまう。

2. 実務で必須の検証チェックリスト

IDトークンを受け取った際、最低限以下のロジックを自前、あるいはライブラリの設定で強制しなければならない。

1. アルゴリズムの固定: alg ヘッダーを信用せず、期待するアルゴリズム(例: RS256 や ES256)のみを許可する。
2. 公開鍵の取得: jwks_uri から公開鍵を取得する際は、必ず信頼できる通信経路(HTTPS)を経由し、鍵のローテーションに対応する。
3. 発行者(iss)の検証: 自分の予期するIdP以外のトークンは即座に破棄する。
4. 対象者(aud)の検証: そのトークンが「自サービス宛」であることを厳密にチェックする。
5. 有効期限(exp)と現在時刻の差分: nbf(Not Before)や iat(Issued At)を含め、時計のズレを考慮した猶予時間(Clock Skew)を適切に設定する。

3. 実践:Python (PyJWT) による堅牢な検証コード

ライブラリを使うにしても、引数の渡し方で強度が変わる。以下は、攻撃者に付け入る隙を与えない、実戦的な実装例だ。

import jwt
import requests
from jwt import PyJWKClient

def verify_oidc_token(token, expected_aud, expected_iss):
    # 1. JWKSエンドポイントから公開鍵を取得(キャッシュ戦略も重要だが今回は割愛)
    url = "https://your-idp.com/.well-known/jwks.json"
    jwks_client = PyJWKClient(url)
    signing_key = jwks_client.get_signing_key_from_jwt(token)

    try:
        # 2. 検証の肝:algorithmsをリストで厳格に指定する
        # これにより、攻撃者がalgヘッダーを書き換えても検証で弾かれる
        payload = jwt.decode(
            token,
            signing_key.key,
            algorithms=["RS256"],  # ここでアルゴリズムを固定!
            audience=expected_aud,
            issuer=expected_iss,
            options={
                "verify_aud": True,
                "verify_iss": True,
                "require": ["exp", "iat", "iss", "aud"] # 必須クレームの強制
            }
        )
        return payload
    except jwt.exceptions.InvalidTokenError as e:
        # ログには詳細を出すが、ユーザーには「認証失敗」とだけ返す(情報漏洩防止)
        print(f"セキュリティ警告: 不正なトークンが検出されました: {e}")
        return None

4. 運用上の盲点:JWKSのキャッシュとリプレイ攻撃

コードが書けても、インフラ側でコケるケースが多い。

  • JWKSのキャッシュ: jwks_uri をリクエストごとに叩くと、IdPに対してDoS攻撃を仕掛けることになり、自身のサービスも停止する。必ず公開鍵をメモリ上でキャッシュし、Cache-Control ヘッダーや kid(Key ID)に基づいて動的に更新する設計にすること。
  • WAFでのブロック: Authorization: Bearer <token> の値をWAFで検査し、長さが異常なものや、既知の攻撃パターン(SQLiやXSSのペイロードがJWT内に埋め込まれたもの)をフィルタリングするルールを適用しておこう。

最後に:セキュリティは「設定」で完成する

エンジニア諸君、セキュリティは「祈り」ではなく「検証可能な構造」だ。
今回示したような検証コードを単にコピーするのではなく、君たちの環境の jwks_uri や issuer を環境変数(.env 等)から読み込み、コード内にハードコーディングしない設計を徹底してほしい。

システムが巨大になればなるほど、認証基盤への攻撃は巧妙化する。だが、基本に立ち返り「入力値(トークン)はすべて悪意がある」という前提で検証を行えば、ほとんどの脆弱性は未然に防げる。

次にコードを書くときは、algorithms 引数が空になっていないか、もう一度確認してくれ。それが、プロのエンジニアの流儀だ。

コメント

タイトルとURLをコピーしました