- 技術 💻
JWTとは|仕組みと、安全に使うための注意点
JWT(JSON Web Token、ジョット)とは、「誰が」「いつまで」といった情報をJSONで表し、改ざんされていないことを署名で確かめられるようにした文字列です。ログインしたユーザーの情報をサーバーからクライアントに渡し、以降のリクエストでそのトークンを送ることで、サーバーは「このリクエストは誰からのものか」を確認できます。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyLTEyMyIsInJvbGUiOiJtZW1iZXIi...
JWTは便利な一方で、「中身は誰でも読める」「一度発行すると取り消しにくい」といった性質を理解せずに使うと、思わぬ問題につながります。この記事では、Python(PyJWT)で実際にトークンを作り、改ざんや期限切れを検証した結果を使って仕組みを解説し、安全に使うための注意点をまとめます。
JWTの早見表
| 項目 | 内容 |
|---|---|
| 正式名称 | JSON Web Token(RFC 7519) |
| 形式 | ヘッダー.ペイロード.署名 を . でつないだ文字列 |
| 中身 | Base64URLでエンコードされているだけで、暗号化されていない(誰でも読める) |
| 守れるもの | 改ざん(署名で検出できる) |
| 守れないもの | 中身の秘密(パスワードや個人情報を入れてはいけない) |
| よく使う場面 | APIの認証(Authorization: Bearer ...)、OAuth・OpenID Connectのトークン |
| 長所 | サーバーがセッションを保存しなくても、トークンだけで検証できる |
| 短所 | 有効期限まで取り消しにくい。トークンが盗まれると、期限まで使われる |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「JWTとは|仕組みと、安全に使うための注意点」
JWTの3つの部分
JWTは、ヘッダー・ペイロード・署名の3つを . でつないだものです。
| 部分 | 中身 | 例 |
|---|---|---|
| ヘッダー | 署名の方式(アルゴリズム) | {"alg":"HS256","typ":"JWT"} |
| ペイロード | ユーザーの情報や有効期限(クレーム) | {"sub":"user-123","role":"member","exp":1790526629} |
| 署名 | ヘッダーとペイロードを、秘密鍵で署名したもの | (バイナリをBase64URLにした文字列) |
ペイロードでよく使う項目(登録済みクレーム)
| 項目 | 意味 |
|---|---|
sub |
対象(ユーザーIDなど) |
exp |
有効期限(この時刻を過ぎたら無効) |
iat |
発行した時刻 |
iss |
発行者(どのサーバーが発行したか) |
aud |
受け取り手(どのAPI向けのトークンか) |
jti |
トークンごとの一意のID(失効の管理に使える) |
実際にJWTを作って中身を見る
PythonのPyJWTで、トークンを作ります。
import jwt
from datetime import datetime, timedelta, timezone
SECRET = "change-me-to-a-long-random-secret-32bytes!!"
now = datetime.now(timezone.utc)
payload = {
"sub": "user-123",
"role": "member",
"iat": now,
"exp": now + timedelta(minutes=15),
}
token = jwt.encode(payload, SECRET, algorithm="HS256")
できたトークンの1つ目と2つ目の部分を、秘密鍵を使わずにBase64URLでデコードしてみます。
import base64
h, p, s = token.split(".")
pad = lambda x: x + "=" * (-len(x) % 4)
print(base64.urlsafe_b64decode(pad(h)).decode())
print(base64.urlsafe_b64decode(pad(p)).decode())
{"alg":"HS256","typ":"JWT"}
{"sub":"user-123","role":"member","iat":1790525729,"exp":1790526629}
秘密鍵がなくても、中身はそのまま読めました。JWTの署名は「改ざんされていないこと」を保証するもので、「中身を隠す」ものではありません。パスワード、個人情報、社内の機密情報をペイロードに入れてはいけません。
署名が改ざんを検出する仕組み
正しいトークンを検証する
jwt.decode(token, SECRET, algorithms=["HS256"])
# {'sub': 'user-123', 'role': 'member', 'iat': 1790525729, 'exp': 1790526629}
ペイロードを書き換えてみる
ペイロードの role を member から admin に書き換え、元の署名のまま送ってみます。
import json
d = json.loads(base64.urlsafe_b64decode(pad(p)))
d["role"] = "admin"
p2 = base64.urlsafe_b64encode(json.dumps(d, separators=(",", ":")).encode()).rstrip(b"=").decode()
jwt.decode(f"{h}.{p2}.{s}", SECRET, algorithms=["HS256"])
jwt.exceptions.InvalidSignatureError: Signature verification failed
中身を1文字でも変えると、署名が合わなくなり、検証に失敗します。秘密鍵を知らない人は、正しい署名を作れません。これが、JWTが改ざんを防げる理由です。
その他の検証結果
| 試したこと | 結果(PyJWTの例外) |
|---|---|
有効期限(exp)を過ぎたトークン |
ExpiredSignatureError: Signature has expired |
| 別の秘密鍵で検証 | InvalidSignatureError: Signature verification failed |
aud が違うトークン |
InvalidAudienceError: Audience doesn't match |
algorithms を指定せずに検証 |
DecodeError: It is required that you pass in a value for the "algorithms" argument |
| 5バイトの短い秘密鍵で署名 | InsecureKeyLengthWarning(SHA256では32バイト以上を推奨する警告) |
alg: none の問題
JWTの仕様には、署名なし("alg": "none")という方式もあります。
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJ1Iiwicm9sZSI6ImFkbWluIn0.
最後の署名の部分が空です。もしサーバーが「トークンのヘッダーに書かれた方式を、そのまま信じて検証する」実装になっていると、攻撃者は署名なしで "role":"admin" のトークンを作れてしまいます。
PyJWTで、HS256だけを許可して検証した結果です。
jwt.decode(none_token, SECRET, algorithms=["HS256"])
# jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
検証するときは、許可するアルゴリズムを必ずサーバー側で指定します。現在の主要なライブラリは、PyJWTのように指定を必須にしているものが多いですが、ライブラリの使い方を確認しておくことが大切です。
HS256とRS256の違い
| 方式 | 鍵 | 向いている場面 |
|---|---|---|
| HS256(HMAC) | 署名と検証に同じ秘密鍵を使う | 発行するサーバーと検証するサーバーが同じ(または同じ組織内) |
| RS256・ES256(公開鍵暗号) | 秘密鍵で署名し、公開鍵で検証する | 複数のサービスで検証する、外部に検証してもらう(OpenID Connectなど) |
RS256やES256では、検証する側は公開鍵だけを持てばよいため、秘密鍵を配る必要がありません。認証サーバーとAPIサーバーが分かれている構成では、こちらが向いています。
アクセストークンとリフレッシュトークン
JWTは、一度発行すると、有効期限まで取り消しにくいという性質があります。そのため、実務では2種類のトークンを組み合わせるのが一般的です。
| 種類 | 有効期限の目安 | 役割 |
|---|---|---|
| アクセストークン | 短い(5〜15分程度) | APIを呼び出すときに毎回送る |
| リフレッシュトークン | 長い(数日〜数週間) | アクセストークンの期限が切れたら、新しいものを発行してもらう |
アクセストークンの期限を短くすることで、盗まれても使われる時間を短くできます。リフレッシュトークンはサーバー側で管理し、使うたびに新しいものに交換する(ローテーション)と、盗まれたリフレッシュトークンが使われたことを検出しやすくなります。
トークンの保存場所
ブラウザでJWTを使う場合、どこに保存するかは重要な判断です。
| 保存場所 | 長所 | 注意点 |
|---|---|---|
| localStorage | 実装が簡単。JavaScriptから自由に使える | XSS(不正なスクリプトの実行)があると、トークンを盗まれる |
Cookie(HttpOnly・Secure・SameSite) |
JavaScriptから読めないため、XSSで盗まれにくい | CSRF対策が必要(SameSite 属性やCSRFトークン) |
| メモリ(変数) | ページを閉じれば消える | 再読み込みで消えるため、リフレッシュトークンとの組み合わせが必要 |
一般的には、リフレッシュトークンは HttpOnly のCookie、アクセストークンはメモリに置く構成が、安全性と使いやすさのバランスがよいとされています。どの方法を選んでも、XSSを防ぐこと自体が最も重要です。
ログアウトと失効の考え方
JWTはサーバーに保存しないため、「ログアウトしたら、そのトークンを無効にする」ことが、セッション方式ほど簡単ではありません。
| 方法 | 内容 |
|---|---|
| 有効期限を短くする | アクセストークンを5〜15分にし、ログアウト時はリフレッシュトークンを無効にする |
| 失効リストを持つ | 無効にしたトークンの jti をRedisなどに保存し、検証時に確認する |
| ユーザーごとの世代番号 | パスワード変更時に番号を上げ、古い番号のトークンを無効にする |
失効リストを使うと、結局サーバー側で状態を持つことになります。「すぐに無効にしたい」要件が強い場合は、セッション方式の方が素直なこともあります。
セッション方式との使い分け
| 観点 | セッション方式 | JWT |
|---|---|---|
| サーバーの状態 | セッションを保存する | 保存しなくても検証できる |
| ログアウト・強制失効 | 簡単(セッションを消すだけ) | 工夫が必要 |
| 複数のサービスでの検証 | 共有のセッションストアが必要 | 公開鍵などで各サービスが検証できる |
| 向いている場面 | 1つのWebアプリ | 複数のAPI・マイクロサービス、スマホアプリ、外部連携 |
1つのWebアプリで完結するなら、従来のセッション方式で十分なことも多いです。JWTを選ぶのは、複数のサービスで認証を共有したいときなど、JWTの長所が活きる場面に絞ると、設計がシンプルになります。
安全に使うためのチェックリスト
| 観点 | チェック項目 |
|---|---|
| 中身 | ペイロードにパスワードや個人情報、機密情報を入れていないか |
| 検証 | 許可するアルゴリズムをサーバー側で指定しているか(alg: none を受け付けないか) |
| 検証 | exp(有効期限)を必ず確認しているか |
| 検証 | 必要に応じて iss(発行者)と aud(受け取り手)も確認しているか |
| 鍵 | HS256の秘密鍵は十分に長いランダムな値か(32バイト以上) |
| 鍵 | 秘密鍵をソースコードに書かず、環境変数や鍵管理サービスで管理しているか |
| 期限 | アクセストークンの有効期限は短いか(5〜15分程度) |
| 保存 | ブラウザでの保存場所を、XSS・CSRFのリスクを踏まえて決めたか |
| 失効 | ログアウトやパスワード変更のときに、どう無効にするかを決めたか |
| 通信 | HTTPSでのみ送っているか。URLにトークンを入れていないか |
よくある質問
Q. JWTの中身を暗号化することはできますか? A. できます。JWE(JSON Web Encryption)という仕様を使うと、中身を暗号化できます。ただし実装が複雑になるため、多くの場合は「秘密の情報をそもそも入れない」ことで対応します。
Q. jwt.ioにトークンを貼り付けても大丈夫ですか? A. jwt.ioはブラウザの中でデコードする仕組みですが、本番環境のトークンを外部のサイトに貼り付けるのは避けた方が安全です。中身の確認は、この記事のようにローカルでデコードできます。
Q. OAuthやOpenID Connectとの関係は? A. OpenID ConnectのIDトークンはJWTの形式です。OAuthのアクセストークンは、JWTの場合も、ランダムな文字列の場合もあります。JWTはトークンの「形式」で、OAuthやOpenID Connectは認可・認証の「仕組み」と考えると整理しやすくなります。
まとめ
- JWTは、ヘッダー・ペイロード・署名を
.でつないだ、改ざんを検出できるトークン - 中身は誰でも読める。秘密の情報を入れない
- 検証では、許可するアルゴリズムを指定し、有効期限を必ず確認する
- アクセストークンは短い期限にし、リフレッシュトークンと組み合わせる
- 保存場所は、XSS・CSRFのリスクを踏まえて決める
- すぐに無効にしたい要件が強いなら、セッション方式も選択肢に入れる
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「JWTとは|仕組みと、安全に使うための注意点」
株式会社RJCでは、安全なWebアプリを作るための知識を、実際に手を動かして確かめながら仲間と学んでいます。こうした知識を共有しながら、一緒に成長を楽しんでいます。
株式会社RJC ― SI事業・SES事業・AI駆動開発。未経験からエンジニアに育てるのが得意な会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。