decodeで分かるのは「書かれている内容」

JWTライブラリのdecodeは、HeaderやPayloadを読み取る処理です。一般的な署名付きJWTのHeaderとPayloadはBase64URLで表現されているだけなので、鍵がなくても読めます。
import jwt from "jsonwebtoken";
const decoded = jwt.decode(token, { complete: true });
console.log(decoded?.header);
console.log(decoded?.payload);
注意: decodeした値は未検証の入力です。画面表示やデバッグには使えても、その`sub`や`role`を根拠にアクセスを許可してはいけません。
verifyは署名とclaimsを検証する
受理するには、API側があらかじめ決めた規則で検証します。
入力サイズと形式を確認
→ 許可したalgか
→ 信頼する鍵で署名を検証
→ iss / aud / exp / 必要ならnbfを検証
→ access tokenなど期待する用途か確認
→ scopeと対象resourceへの認可を確認
→ 設計上必要なら失効状態も確認
ヘッダーに書かれたalgへ検証方法を任せず、検証側で許可するアルゴリズムを固定します。鍵もissなど信頼できる設定と結び付け、攻撃者が指定した場所から取得しません。
const payload = jwt.verify(token, trustedKey, {
algorithms: ["RS256"],
issuer: "https://auth.example.com",
audience: "orders-api",
});
if (typeof payload !== "object" || typeof payload.exp !== "number") {
throw new Error("exp claim is required");
}
jsonwebtokenは存在するexpを検証しますが、このAPIでexpを必須にする確認は別に示しています。このほかtypや独自claim、許容する時刻差、最大寿命、scopeなど、発行元とAPIの契約に必要な条件を追加します。IDトークン、access token、パスワード再設定用トークンを同じ検証規則で受け入れないことも重要です。
「検証成功」と「操作を許可する」は別
署名とclaimsの検証に成功したら、そのトークンが設定した条件を満たすと判断できます。それでも、利用者が特定の注文を編集できるかなど、対象resourceへの認可は別に確認します。
また、期限前の失効方法はトークン形式だけでは決まりません。短い寿命だけで運用する、jtiのdenylistやsession versionを照会する、認可サーバーへintrospectionするなど、採用した方式に従います。盗まれたトークンをいつまで使えるかも、期限だけでなく失効確認・sender-constrained token・API側の検証規則によって変わります。
まとめ
- decodeはHeaderとPayloadを読むだけで、真正性を確認しない
- verifyではalg、署名、iss、aud、exp、用途などを期待値と照合する
- 検証済みtokenでも、対象resourceへの認可は別に行う
- 失効の確認要否と方式を、システムの契約として決める
JWTの一般的な構造、JWSとJWE、tokenの種類はJWTの仕組みへ、複数サービスで検証するときの鍵配布やaudience設計はマイクロサービスでJWTが使われる理由へ分けて解説しています。