【実務・中級編】JWTのiat(Issued At)とnbf(Not Before)クレームの検証 – アプリケーションセキュリティ & 安全な開発防御ガイド

JWTの「検証漏れ」が招く悲劇:iatとnbfで防ぐ、泥臭いリプレイ攻撃の現実

現場のエンジニア諸君、お疲れ様。最近、設計レビューで「JWTの中身をデコードしてユーザーIDさえ見ていれば安心」というコードを散見する。正直に言おう。それは鍵のかかっていない玄関に、住所と氏名を書いた名札を貼り付けているのと同じだ。

JWT(JSON Web Token)は便利だ。ステートレスでスケーラビリティも高い。だが、多くの開発者が「署名検証」だけで満足し、「時間の検証」という最後の砦を忘れている。今回は、攻撃者が虎視眈々と狙う「リプレイ攻撃」を無効化する、`iat`と`nbf`の正しい実装について語ろう。

なぜ「署名」だけでは不十分なのか?

攻撃者の視点に立ってみよう。もし君が開発したアプリケーションで、署名が正当なJWTを、通信経由で盗聴(中間者攻撃)やログからの流出で手に入れたとしよう。

攻撃者は、そのトークンがいつ発行されたか、いつから使えるかという「時間的制約」を無視して、そのトークンを何度でも使い回す。これが「リプレイ攻撃」だ。もし、君のシステムでトークンの有効期限(`exp`)が長めに設定されていたらどうなるか? 攻撃者はその間、正規のユーザーになりすましてやりたい放題だ。

ここで登場するのが、以下の2つのクレームだ。

  • `iat` (Issued At): 発行時刻。これが「未来」になっているトークンは、サーバーの時計が狂っているか、悪意ある工作の可能性が高い。
  • `nbf` (Not Before): 有効開始時刻。この時刻に達していないトークンを弾くことで、トークンの「フライング利用」を阻止できる。

実践:リプレイ攻撃を防ぐ実装(Node.js / jsonwebtoken)

教科書通りの検証ではなく、現場で「泥臭く」弾くための実装例だ。`jsonwebtoken` ライブラリを使う場合、単純に `verify` するだけでは不十分なケースがある。

const jwt = require(‘jsonwebtoken’);

/

  • 堅牢なJWT検証関数

/
function verifyToken(token) {
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET, {
// 許容するClock Skew(サーバー間の微小な時間差)は最大でも数秒に絞る
clockTolerance: 5,
// iatが未来の日付であるトークンを即座に拒否
ignoreNotBefore: false,
});

// ここからが現場の知恵:
// iatが現在時刻より未来の場合は不正とみなす
const now = Math.floor(Date.now() / 1000);
if (decoded.iat > now) {
throw new Error(‘未来の日付で発行されたトークンです。’);
}

// nbfが未来の場合はまだ使用不可
if (decoded.nbf && decoded.nbf > now) {
throw new Error(‘トークンが有効になる時刻に達していません。’);
}

return decoded;
} catch (err) {
// ログには詳細を出すが、クライアントには最小限の情報のみ返す
console.error(`[Security Alert] JWT Verification Failed: ${err.message}`);
throw new Error(‘Unauthorized’);
}
}

攻撃者の盲点:Clock Skew(時計のズレ)を突く

インフラ担当者なら知っているはずだが、分散システムにおいてサーバー間の時間は必ずズレる。この「ズレ」を考慮して、ライブラリには `clockTolerance`(許容誤差)という設定がある。

しかし、ここを甘く見てはいけない。この値を大きく設定すればするほど、攻撃者がリプレイ攻撃を仕込める「隙間」が広がる。

  • 鉄則: 同一データセンター内であれば、許容誤差は1秒〜5秒で十分だ。数分単位のズレを許容しているなら、それはNTPサーバーの同期設定を見直すべきインフラの脆弱性であると認識してほしい。

最後に:セキュリティは「多層防御」で完結する

JWTの検証を完璧にしても、トークンが盗まれてしまえば意味がない。だからこそ、以下の項目を常にチェックリストに入れておいてくれ。

1. Transport Layer: 必ずTLS(HTTPS)で通信すること。
2. Storage: ブラウザのLocalStorageではなく、`HttpOnly` かつ `Secure` 属性付きのCookieに保存すること(XSS対策)。
3. Rotation: そもそもトークンの有効期限(`exp`)は可能な限り短くし、リフレッシュトークンで運用する設計にすること。

コードは動けばいいものではない。攻撃者が「このシステム、隙がないな」と諦めてターゲットを他所に変える。それこそが、我々エンジニアが目指すべきゴールだ。

今日の解説が、君たちの現場の堅牢性を一ミリでも引き上げることを願っている。何か不明点があれば、またいつでも聞いてくれ。現場からは以上だ。

コメント

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