認証の要「JWT」を正しく扱う——alg, typ, kidが語るセキュリティの深淵
ネットワークエンジニアとして現場に立っていると、「なんとなく動く」という状態がいかに危ういかを痛感させられる場面に何度も遭遇する。特にWeb APIにおける認証の要であるJWT(JSON Web Token)は、その手軽さゆえに実装ミスが生まれやすい箇所だ。
今日は、JWTのヘッダーという「小さな領域」に刻まれた alg、typ、kid という3つのパラメータが、いかにして堅牢な認証を支えているのか、そして我々エンジニアが陥りやすい「alg: none の罠」について、現場の視点から紐解いていこう。
—
1. ヘッダーが語る「素性」:alg, typ, kid の役割
JWTは、Header . Payload . Signature の3つのパートがドットで繋がれた文字列だ。このうち、最初の Header は、このトークンが「何者か」を定義するメタデータである。
alg (Algorithm)
署名アルゴリズムを指定する。HS256(HMAC + SHA256)のような共通鍵方式か、RS256(RSA + SHA256)のような公開鍵方式かがここに記される。ここが全てのセキュリティの起点だ。
typ (Type)
トークンの種別。RFC 7519では JWT と記述することが推奨されている。受信側はこの値を見て、処理対象がJWTであるかを早期判定する。
kid (Key ID)
これが極めて重要だ。特に鍵のローテーションを行う大規模なインフラでは必須となる。kid は、検証に使用すべき「鍵」を特定するためのIDだ。サーバー側が複数の鍵を管理している場合、このIDを見て適切な公開鍵を選択する。
—
2. 禁断の「alg: none」攻撃を知る
かつて、世界中のWeb APIが震撼した脆弱性がある。それが alg: none だ。
仕組みは単純だ。攻撃者がJWTのヘッダーを {"alg": "none", "typ": "JWT"} に書き換え、署名部分を空にしてサーバーに送りつける。一部の古いライブラリや不適切な実装では、alg が none であることを「署名検証不要」と解釈し、認証をパスさせてしまった。
教訓: ライブラリのデフォルト挙動を信じてはいけない。コードレベルで「許可するアルゴリズム」をホワイトリスト化することが絶対条件だ。
# 安全な実装例: 許可されたアルゴリズム以外は即座に拒否する
import jwt
def verify_token(token, public_key):
try:
# algorithms引数で許容するアルゴリズムを明示的に指定する
return jwt.decode(
token,
public_key,
algorithms=["RS256"] # HS256など他の方式を排除
)
except jwt.InvalidAlgorithmError:
# ログに記録し、不正アクセスとして検知する
print("警告: 許可されていないアルゴリズムのトークンを検知しました")
raise
—
3. 実践:kid を使った鍵のローテーション運用
kid を活用すると、秘密鍵を漏洩させずに安全に更新できる。サーバー側は JWKS(JSON Web Key Set)という公開鍵のリストを公開し、クライアント(またはリソースサーバー)は kid をキーにして適切な鍵を探すというフローだ。
curl で JWKS を確認する
まず、公開鍵セットが正しく提供されているか確認しよう。
# Authサーバーが公開しているJWKSエンドポイントを取得
curl -s https://auth.example.com/.well-known/jwks.json | jq .
ヘッダーの構造(デバッグ用)
トークンをデコードした際、ヘッダーに kid が含まれていることを確認するのが鉄則だ。
{
"alg": "RS256",
"typ": "JWT",
"kid": "prod-key-2023-10"
}
—
4. 現場でのトラブルシューティング Tips
インフラ側でJWTの認証トラブルに遭遇した際、私はまず以下の切り分けを行う。
1. 署名失敗か、有効期限切れか?
Signature verification failedであれば、kidが示す鍵と署名に使った鍵の不一致(またはalgの不整合)を疑う。Token expiredであれば、expクレームとサーバー時刻のズレ(NTPの同期状態)を確認する。
2. ヘッダーの確認
jwt.ioなどのデバッガーを使うのも手だが、本番環境のトークンを貼るのは厳禁だ。必ずbase64デコードでヘッダー部分だけを抜き出して確認する癖をつけよう。
# JWTの最初のドットまでを抽出し、ヘッダーをデコードするコマンド
echo "トークン文字列" | cut -d. -f1 | base64 -d 2>/dev/null | jq .
—
結び
JWTは単なる文字列ではなく、あなたのシステムの堅牢性を証明する「身分証」だ。alg を固定し、kid で鍵を管理し、typ を正しく指定する。これら一つひとつの仕様を理解することは、複雑な分散システムにおいても「何が正しい通信か」を即座に判断できる、エンジニアとしての鋭い勘を養うことに繋がる。
RFC 7519(JWT)や RFC 7515(JWS)の仕様書は一見難解だが、現場でパケットを追いかけているとき、それらの行間にある意図がクリアに見えてくるはずだ。ぜひ、自身の実装を見直す機会にしてほしい。
コメント