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期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?