【実務・中級編】JWTのクレーム検証(exp, nbf, aud)の重要性と実装 – アプリケーションセキュリティ & 安全な開発防御ガイド

JWTは「信頼のパスポート」だ。検証を怠ることは、入国審査官を解雇するに等しい

現場でコードをレビューしていると、JWT(JSON Web Token)を単なる「Base64でエンコードされた文字列」と勘違いしているエンジニアによく出くわす。特に「署名さえ合っていればOK」という甘い考えが、どれほど多くのシステムを食い物にしてきたか。

結論から言おう。exp(有効期限)、nbf(開始時間)、aud(オーディエンス)の検証をサボることは、鍵のかかっていない玄関を開けっ放しにして、「泥棒は入らないはずだ」と祈っているのと同じだ。

今日は、なぜこれらが重要なのか、そして実務でどう「死守」すべきかを徹底的に叩き込む。

—

なぜ「署名の検証」だけでは足りないのか?(攻撃者の視点)

攻撃者は、あなたのシステムが「署名しか見ていない」という脆弱性を突くために以下の手法を用いる。

1. リプレイ攻撃(Replay Attack)

もし exp を検証していなければ、一度盗み出したトークンは「永遠に有効なフリーパス」となる。通信を傍受した攻撃者は、期限切れのトークンを使い回して、半年後でもあなたのユーザーになりすますことが可能だ。

2. トークンの悪用(Confused Deputy Problem)

aud を検証しない場合、攻撃者が別のサービス(例えば攻撃者が管理する悪意あるAPI)で発行させたトークンを、あなたのアプリケーションに送りつける。あなたのアプリが「署名は正しいから」と無批判に受け入れれば、攻撃者の意図通りに権限を昇格・悪用される。

—

実践:セキュアな検証の実装コード

ここでは、最も普及しているJWTライブラリを用いた「正しい検証」の実装例を示す。重要なのは「ライブラリのデフォルト値に依存せず、明示的に検証項目を定義すること」だ。

Node.js (jsonwebtokenライブラリ) の場合

多くのエンジニアが jwt.verify(token, secret) とだけ書いて満足するが、それだけでは足りない。

const jwt = require(‘jsonwebtoken’);

const verifyToken = (token) => {
try {
// 重要なのは verify のオプションで検証項目を「強制」すること
const decoded = jwt.verify(token, process.env.JWT_SECRET, {
algorithms: [‘HS256’], // 脆弱なアルゴリズム(none等)を排除
audience: ‘my-app-web-api’, // aud: このアプリ専用のトークンか?
issuer: ‘auth-server-v1’, // iss: 信頼できる発行元か?
clockTolerance: 30 // サーバー間の時刻ズレを許容(秒)
});

// さらに、プログラム側で payload の内容を厳密にチェックする
if (!decoded.sub) throw new Error(‘Subject missing’);

return decoded;
} catch (err) {
// ここでエラーログを残すことがインシデント検知の第一歩
console.error(‘JWT Verification Failed:’, err.message);
throw new Error(‘Unauthorized’);
}
};

Python (PyJWT) の場合

Python界隈でも同様だ。options を指定し、検証項目を厳格化する。

import jwt

def verify_token(token):
try:
# algorithmsを明示しないと、none攻撃の隙が生まれる
payload = jwt.decode(
token,
“YOUR_SECRET_KEY”,
algorithms=[“HS256″],
audience=”my-app-web-api”,
issuer=”auth-server-v1″,
require=[“exp”, “aud”, “iss”] # これらが存在しないトークンは即座に弾く
)
return payload
except jwt.ExpiredSignatureError:
# 有効期限切れの処理
print(“Token expired”)
except jwt.InvalidTokenError:
# 不正なトークンの処理
print(“Invalid token”)

—

インフラ層での防御:WAFを活用せよ

コードの実装が完璧でも、脆弱性のあるライブラリを使っていたり、実装漏れがある場合に備えて、WAF(AWS WAF等)でJWTをフィルタリングするのも現代の常套手段だ。

特に、Authorization ヘッダーの中身を正規表現で検査し、明らかに異常なフォーマットや、極端に長いトークンを遮断するルールを適用しておこう。

  • ルール設計のヒント:
  • トークンが存在しないリクエストの拒否(必要なエンドポイントのみ)
  • トークン内の exp クレームをデコードして、現在時刻と比較するカスタムルール(WAFの設定次第だが非常に有効)

—

最後に:先輩からのアドバイス

JWTは便利だが、その利便性は「検証の厳格さ」と引き換えだ。

1. alg: none を許すな: ライブラリの設定で、必ず使用するアルゴリズムをホワイトリスト形式で指定すること。
2. 時刻同期を軽視するな: サーバーのNTP設定がズレているだけで、正しいトークンが拒否されたり、逆に期限切れが通ったりする。これは運用上の重大な脆弱性だ。
3. 「とりあえず動く」から脱却せよ: 開発環境で検証を無効にするのは良いが、それを本番環境のconfigに紛れ込ませるミスが最も多い。環境変数で JWT_STRICT_MODE=true を強制し、これが有効でない限りサーバーが起動しない設計にするのが、真のプロの仕事だ。

セキュリティとは、魔法のような防御技術のことではない。こうした「当たり前のチェック」を、泥臭く、徹底して積み重ねる執念のことだ。さあ、今すぐ君のコードの verify 部分を確認してくれ。そこに穴はないか?

コメント

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