ユーザーが SaaS アプリケーションにログインするたびに、認証トークンが生成され、ブラウザやモバイルアプリに渡されます。そのトークンの内部には、静かに刻み続けるクロックが埋め込まれています。JWT の有効期限フィールドは、プレーンな Unix タイムスタンプとして格納されており、そのトークンを読み取るすべてのサーバーに対して、いつ認証情報を信頼しなくなるべきかを正確に伝えます。このタイムスタンプがほんの数秒ずれるだけで、ユーザーが早々にログアウトされてしまうか、あるいはセキュリティポリシーで許可されている以上に長くセッションが有効になってしまうという深刻な問題が発生します。JSON Web Token のクレーム、特に exp と iat の仕組みを正しく理解することは、認証に余計な摩擦を加えることなくセキュリティを強化するうえで、SaaS チームにとって最も実践的な取り組みの一つです。
重要なポイント:
-
JWT JSON Web Token における
expとiatクレームは、常にミリ秒ではなく秒単位の整数による Unix タイムスタンプです。 - サーバー間のクロックスキューは、トークンを意図せず無効化したり、ポリシーを超えてセッションを延長したりする原因になります。業界標準の対策は 60 秒の許容ウィンドウを設けることです。
- トークンの有効期限を長く設定しすぎると侵害リスクが高まり、短く設定しすぎるとユーザー体験を損ないます。適切な値を選ぶための具体的な計算式があります。
- JSON Web Token のタイムスタンプは、ライブラリ不要で Unix エポックコンバーターを使うだけで、秒単位でデコード・検証できます。
目次
JWT とは何か、なぜタイムスタンプが内部に存在するのか
JSON Web Token は、ドットで区切られた 3 つの Base64 エンコード部分で構成されるコンパクトな URL セーフの認証情報です。その 3 つとは、ヘッダー、ペイロード、署名です。時間に依存するデータが格納されるのはペイロードです。JWT はステートレスであるため、サーバーはトークンの検証にデータベースのセッションを参照しません。代わりに、ペイロードに埋め込まれたクレームを読み取り、暗号署名を信頼します。この設計は高速でスケーラブルですが、重要な制約をもたらします。一度トークンが発行されると、サーバーはそれを「削除」できません。有効期間を制限する唯一の信頼できる方法は、有効期限タイムスタンプをトークン自体に直接埋め込むことです。
これが、Unix エポック時間が JWT セキュリティの中心にある理由です。JWT を定義する RFC 7519 仕様では、時間に関連するクレームを「NumericDate」値として表現することが義務付けられています。これは単純に、1970 年 1 月 1 日 UTC からの経過秒数を表す整数です。タイムゾーンもロケールのフォーマットも不要です。世界中のどのサーバーでも自身のクロックと比較できる整数値のみです。
exp と iat クレームの解説
JWT 仕様にはいくつかの登録済みクレームが定義されています。トークンのライフサイクル管理において特に重要なのは次の 2 つです:
-
exp(有効期限): このタイムスタンプ以降、トークンは受け付けられてはなりません。現在時刻がこの値以上の場合、サーバーはトークンを拒否する必要があります。これが JWT 有効期限の中核的なメカニズムです。 -
iat(発行日時): トークンが生成された時刻のタイムスタンプです。iat クレームはデフォルトでは強制されませんが、非常に有用です。トークンの経過時間を計算したり、expとは独立して最大有効期間ポリシーを適用したり、不審に過去に発行されたトークンを検出したりするのに役立ちます。
3 番目のクレームである nbf (Not Before) は、トークンが有効になる最も早い時刻を定義します。使用頻度は低いですが、同じ Unix タイムスタンプ形式に従います。
これら 3 つの値はすべて整数です。JavaScript の Date.now() を使用してミリ秒単位で生成してしまうのはよくあるミスで、その場合の値は期待値の約 1,000 倍になります。現在のエポック秒に対して exp を検証するサーバーは、西暦 33,658 年に有効期限が切れるトークンとして認識し、決して拒否しません。これは複数の SaaS チームで実際に発生している本番環境のバグです。
具体例: 実際のトークンタイムスタンプをデコードする
ユーザーが 2025 年 7 月 1 日 UTC 午前 10 時にログインした後、認証サービスが次の JWT ペイロードを発行したとします:
{
"sub": "user_8821",
"iat": 1751364000,
"exp": 1751367600,
"role": "admin"
}
これらの数値が何を意味するかを確認してみましょう:
| クレーム | Unix タイムスタンプ | 人間が読める形式 (UTC) | 意味 |
|---|---|---|---|
iat
|
1751364000 | 2025-07-01 10:00:00 | この時刻にトークンが発行された |
exp
|
1751367600 | 2025-07-01 11:00:00 | ちょうど 1 時間後にトークンが期限切れになる |
差はちょうど 3,600 秒、つまり 1 時間です。UTC 午前 11 時 00 分 00 秒以降にこのトークンを受け取ったサーバーは、必ず拒否しなければなりません。手動で確認するには、iat を exp から引きます: 1751367600 - 1751364000 = 3600 秒。Unix タイムスタンプコンバーターを使えば、これらの値の人間が読める形式への変換を即座に確認できます。これは前述のミリ秒と秒の混同バグを事前に防ぐための、素早いサニティチェックとして最適です。
データベースにこれらの値を格納する方法については、データベースにおける Unix タイムスタンプのガイドもご参照ください。exp をトークンに格納する場合も監査ログテーブルに格納する場合も、同じ整数形式が適用されます。
クロックスキュー: 静かなるトークンの破壊者
チームが見落としがちな実際の制約があります。分散システムでは、認証サーバーと API サーバーは別々のマシンです。それらのシステムクロックは NTP で同期されていますが、完全に一致することはありません。クラウド環境では 30 秒から 90 秒の差が生じることは珍しくありません。
次のシナリオを考えてみてください: exp = 1751367600 のトークンが、クロックの読みが 1751367610 - つまり有効期限の 10 秒後 - の API サーバーで検証されます。トークンは拒否されます。ユーザーには 401 エラーが返ります。ユーザーの視点からすると、ログインしたばかりなのにアプリが壊れたように見えます。
標準的な対策は、バリデーションロジックにクロックスキューの許容範囲を組み込むことです。ほとんどの JWT ライブラリは leeway パラメーターをサポートしています。60 秒という値は広く受け入れられており、OpenID Connect Core 仕様でも参照されています。この値を設定し、ドキュメントに記録し、スタック内のすべてのサービスで同じ値を使用することを確認してください。
実践的なルール:
exp の検証には 60 秒の leeway を追加してください。iat チェックには leeway を追加してはいけません。未来に発行されたトークンを受け入れることになり、リプレイ攻撃の危険信号となるからです。
SaaS プロダクトに最適なトークン有効期限の選び方
適切なトークン有効期限の値は、アプリケーションの機密性と想定されるセッションの挙動によって異なります。以下は実践的なフレームワークです:
- 機密性の高いアプリ (銀行、医療、管理ダッシュボード): アクセストークンは 15 分から 30 分。リフレッシュトークンを使ってセッションをサイレントに更新します。
- 標準的な SaaS アプリ (プロジェクト管理、CRM、アナリティクス): 1 時間から 8 時間。典型的な作業セッションの長さに合わせます。
- 低リスクまたは読み取り専用 API: 最大 24 時間ですが、機密性の高い操作ではトークンローテーションと組み合わせます。
- マシン間サービストークン: より長い期間 (数日または数週間) も可能ですが、スコープを狭く絞り、スケジュールに従ってローテーションする必要があります。
発行時に exp の値を計算するための便利な計算式:
// Node.js の例
const iat = Math.floor(Date.now() / 1000); // 現在時刻を秒単位で取得
const ttl = 3600; // 有効期間: 1 時間を秒単位で
const exp = iat + ttl;
const payload = {
sub: userId,
iat: iat,
exp: exp
};
ミリ秒を秒に変換するための 1000 による明示的な除算に注目してください。この 1 行が、JavaScript アプリケーションで最も多い JWT タイムスタンプのバグを防ぎます。
セキュアな JWT 有効期限を実装するための具体的な手順
今日から任意の SaaS 認証システムに適用できる具体的なチェックリストです:
-
iatとexpの両方を常に含める。iatクレームは技術的にはオプションですが、監査証跡を提供し、有効期限とは独立して最大有効期間の適用を可能にします。 -
デプロイ前にタイムスタンプの形式を検証する。 トークンをデコードして、
expが 10 桁の整数であることを確認します。13 桁の値はミリ秒が混入していることを意味します。 - バリデーションミドルウェアに 60 秒のクロックスキュー leeway を設定する。 認証トークンを使用するすべてのサービスで一貫して適用します。
- リフレッシュトークンと組み合わせた短命のアクセストークンを使用する。 15 分のアクセストークンと 7 日間のリフレッシュトークンの組み合わせは、単一の 7 日間アクセストークンよりはるかに安全です。リフレッシュトークンはサーバー側で失効させることができるからです。
-
認証済みリクエストのたびに
iatの値をログに記録する。 これにより、同一トークンが同時に 2 つの異なる地理的地域から使用されるなどの異常を検出できます。 -
ステージング環境で時間を早送りして有効期限をテストする。 クロックを
expの値を超えた時刻にモックして、アプリがクラッシュやループではなく 401 を適切に処理することを確認します。
トークンから取得した生の exp 値を読みやすい日付に変換したり、特定の期間に対して設定すべきタイムスタンプを確認したりする場合は、ホームページのエポックコンバーターが双方向の変換を即座に処理します。コードは一切不要です。
まとめ
JWT の有効期限は複雑な概念ではありませんが、小さなミスが大きなセキュリティの隙間を生む領域です。exp と iat クレームは単なる整数、つまりエポックからの経過秒数で表される Unix タイムスタンプです。そのシンプルさが強みです。すべてのサーバー、すべての言語、すべてのプラットフォームが、あいまいさなく 2 つの数値を比較できます。真のスキルは、それらを正しく適用することにあります。適切なトークン有効期限の期間を選択し、許容ウィンドウでクロックスキューを処理し、ミリ秒と秒の混同ミスを本番環境に出る前に発見することです。これらの習慣を認証パイプラインに組み込めば、JSON Web Token の実装はセキュアかつ保守しやすいものになります。
JWT タイムスタンプをコード不要で即座に検証
JWT ペイロードの Unix タイムスタンプを貼り付けるだけで、ワンクリックで人間が読める日付に変換できます。ミリ秒と秒の混同エラーを本番環境に出る前に発見しましょう。
無料ツールを試す →
exp (有効期限) は、それ以降トークンが無効となり拒否されなければならない Unix タイムスタンプです。iat (発行日時) は、トークンが生成された時刻のタイムスタンプです。どちらも秒単位の整数です。iat クレームはオプションですが、監査やトークンの最大有効期間ポリシーの適用に非常に役立ちます。
Unix タイムスタンプはタイムゾーンに依存しない整数であるため、すべてのサーバーとリージョンにわたって曖昧さがありません。「2025-07-01T10:00:00」のようなフォーマットされた日付文字列は、ロケールやタイムゾーンの設定によって誤解される可能性があります。また、整数の比較は、スループットの高い認証システムにおける文字列のパースよりも高速でエラーが起きにくいという利点もあります。
有効期間の長いトークンは、ユーザーのアカウントが侵害されたり権限が変更されたりしても有効なままです。JWT はステートレスであるため、サーバーは有効期間の途中でトークンを失効させることができません。攻撃者が 30 日間有効なトークンを盗んだ場合、30 日間のアクセス権を持つことになります。短い有効期限とリフレッシュトークンを組み合わせることで、このリスクウィンドウを大幅に縮小できます。
JavaScript では、iat や exp の値を生成する際は常に Math.floor(Date.now() / 1000) を使用してください。生の Date.now() はミリ秒を返します。1000 で割ることで、JWT 仕様が要求する秒ベースの整数に変換されます。タイムスタンプが 13 桁ではなく 10 桁の数値であることを確認することで検証できます。
クロックスキューとは、分散システムにおける 2 つのサーバーのシステムクロック間の小さな時間差のことです。わずか 30 秒の差でも、検証サーバーのクロックがわずかに進んでいる場合、有効なトークンが拒否される可能性があります。標準的な対策は、JWT バリデーションライブラリに 60 秒の leeway を設定して、通常の NTP ドリフトを吸収することです。