はじめに
現代のソフトウェア開発では、API(Application Programming Interface)があらゆるサービスの中心的役割を担うようになりました。
この急速な普及の背景には、「単一の API で複数のインターフェイスを支えられる」という構造的メリットがあります。
APIの台頭
1つのAPIで複数プラットフォームをサポート
従来は、Web、スマホアプリ、デスクトップアプリなど、クライアントごとに個別のロジックを書く必要がありました。
しかし API 方式では、
- Web アプリ
- モバイルアプリ
- IoT デバイス
といった異なるクライアントが、同じ API にアクセスして同じサーバーロジックを共有できます。
その結果として、
- 重複開発の削減
- 保守性の向上
- アプリ間の仕様ズレ防止
といったメリットが生まれ、開発効率は飛躍的に高まりました。
セキュリティ上の利点
API を中心に据えた設計では、認証・認可といった重要なセキュリティ処理をサーバ側に一元管理できます。
これにより、クライアントごとにバラバラな実装になることを避けられます。
つまり、
どのクライアントから来ても、API が同じセキュリティルールで守ってくれる。
という状態を作れるわけです。
歴史と成り立ち
| 年 | 出来事 |
|---|---|
| 2010〜2012年頃 | OAuth 2.0 や OpenID Connect の構想期。セッション管理の課題を解決するため、「自己完結型トークン」への関心が高まる。 |
| 2014年 | JWT の草案(draft)が出され、API 認証界隈で実装が増える。 |
| 2015年5月 | RFC 7519 “JSON Web Token (JWT)” が正式発行。 |
| 2017年以降 | OpenID Connect Core 1.0 で採用され、OAuth 2.0 の Access Token / ID Token 形式としてデファクト化。 |
| 現在 | API、SSO、モバイルバックエンド、マイクロサービス間通信などで標準的な手段に。 |
JWTの構造(3つのパーツで構成)
JWT は次の 3 つのパートを Base64URL エンコードし、「.」で連結したものです。
header.payload.signature
1. Header(ヘッダー)
主なフィールド:
typ: "JWT"- 使用する署名アルゴリズム(例:
HS256,RS256)
例:
{
"typ": "JWT",
"alg": "HS256"
}
2. Payload(ペイロード)
JWT の本体。ユーザー情報などの クレーム(Claim) が含まれます。
主な種類:
-
Registered Claims(登録済みクレーム)
iss,sub,aud,expなど標準で定義されたもの -
Public Claims / Private Claims
開発者が自由に定義するカスタム情報(例:role,"admin": 1など)
※この記事では、セキュリティ上重要なポイントに焦点を当てるため、細かい分類の深掘りは省略します。
例:
{
"username": "user",
"admin": 0
}
3. Signature(署名)
署名は JWT の信頼性を保証する最重要部分 です。
signature = Sign(
base64url(header) + "." + base64url(payload),
secret_or_private_key
)
これにより、攻撃者がペイロードを勝手に書き換えても、
署名検証に失敗してトークンが無効化されます。
署名アルゴリズムの種類
JWT で使われる署名アルゴリズムはいくつかありますが、実務で特に重要なのは次の 3 つです。
1. None
- 「署名なし」の意味
- 実質 偽造し放題 の危険な状態
- 本来は特殊環境向けだが、実装を誤ると重大な脆弱性になる
2. Symmetric(対称鍵署名:HS256 など)
- 共有秘密鍵(single secret)で署名
- 検証も同じ秘密鍵で行う
弱い秘密鍵を使うと、オフライン総当たり攻撃で突破される危険があります。
3. Asymmetric(公開鍵暗号:RS256 など)
- 署名:秘密鍵(private key)
- 検証:公開鍵(public key)
アプリ側が公開鍵を持っていれば、
認証サーバが署名した JWT を安全に検証できます。
よくあるミス
- 機密情報の開示(ペイロードにパスワード/ハッシュなどを入れる)
- 署名を検証しない(単に
base64デコードして中身だけ信じる) -
alg: "none"へのダウングレードを許容 - HS256 で 弱い秘密鍵 を使い、
hashcat -m 16500などでオフライン総当たり可能 - RS256 ↔ HS256 アルゴリズム混同(公開鍵を HS の「秘密鍵」として扱う)
-
exp未設定・過度に長寿命なトークン -
aud未検証のクロスサービス中継(Cross-Service Relay) -
adminなどの権限クレームを、DB 再チェックなしでそのまま信じる
テスト観点チェックリスト(Red / Blue 両利き)
攻撃者視点(レッドチーム)
- 署名未検証の疑い:署名部分を壊す/削除しても通るか
-
alg: "none":Header を改ざんしても API が受け付けるか -
HS256 弱鍵:
hashcat -m 16500などで辞書攻撃を試せるか -
RS→HS 混同:
algを HS に変更し、「公開鍵」を HS 用の秘密鍵として指定しても通るか -
期限まわり:
expなし/過去・未来のiat/nbfでバイパスできないか -
Audience:
audが実サービスで検証されているか(他サービスからの越境利用ができないか) -
保存場所:
localStorageに保存していて、XSS で窃取できそうか -
High-risk クレーム:
adminなどのフラグが真偽の単一ソースになっていないか(DB 側で再確認しているか)
実装者視点(ブルーチーム)
-
アルゴリズム固定(受け入れる
algをホワイトリストで固定) - 鍵種別の分離(RS は公開鍵・秘密鍵、HS は共有秘密…を混同しない)
-
短寿命 Access Token + Refresh Token ローテーション
+ 失効リスト(jti)によるトークン無効化 -
iss/aud/sub/nbf/iat/expを 必須クレームとして検証 -
権限はサーバ側で再確認
(トークン内のadminだけを真実として扱わない) -
Cookie (HttpOnly, Secure, SameSite) での保存
または メモリ上の保持+CSRF対策 を組み合わせる - キー管理:KMS / Vault などによる保護、ローテーション、キーバージョンの管理、短期失効
-
観測・監査:署名失敗回数、
kid不正利用、異常なaudなどのメトリクスを監視
開発者向け安全雛形(Python / Flask + PyJWT)
from flask import Flask, request, jsonify
import jwt, os, datetime
SECRET = os.environ["JWT_HS_SECRET"]
ISS = "https://auth.example.com"
AUD = "appA"
app = Flask(__name__)
def issue_access(sub: str) -> str:
now = datetime.datetime.now(datetime.timezone.utc)
payload = {
"iss": ISS,
"sub": sub,
"aud": AUD,
"iat": now,
"nbf": now,
"exp": now + datetime.timedelta(minutes=10),
}
return jwt.encode(payload, SECRET, algorithm="HS256")
def verify_access(token: str) -> dict:
return jwt.decode(
token,
SECRET,
algorithms=["HS256"],
audience=[AUD],
issuer=ISS,
options={"require": ["iss", "sub", "aud", "iat", "nbf", "exp"]},
)
@app.get("/me")
def me():
auth = request.headers.get("Authorization", "")
if not auth.startswith("Bearer "):
return jsonify({"error": "No token"}), 401
token = auth.split(" ", 1)[1]
try:
claims = verify_access(token)
return jsonify({"sub": claims["sub"], "aud": claims["aud"]})
except Exception as e:
return jsonify({"error": str(e)}), 401
まとめ
- アルゴリズム固定と鍵種別の厳格運用(RS と HS を混ぜない)
-
短寿命 Access Token + Refresh Token ローテーション
+exp/iat/nbf/iss/aud/subの必須化・検証 -
audを実サービス側で検証し、クロスサービス中継を遮断 - 権限はサーバ側で再確認し、トークンの
adminを盲信しない - キー管理と監査(KMS・ローテーション・署名失敗検知・
kid監視)
Tools & Documents
- https://www.postman.com/
- https://swagger.io/
- https://datatracker.ietf.org/doc/html/rfc7519#section-3.1
- https://gchq.github.io/CyberChef/
- https://www.jwt.io/
- https://pyjwt.readthedocs.io/en/stable/
- https://hashcat.net/hashcat/
- https://www.openwall.com/john/