セキュリティ・ID

セッションとJWTの違い|署名を確かめないと、admin を true にしたトークンが通る

セッションとJWTは何がどう違うのかJWTは「ログアウトできない」と聞くが本当か自分のアプリではどちらを選べばいいのか

ログイン状態を保つ方法は、大きく2つあります。サーバー側に記録を残すセッションと、利用者側にトークンを持たせるJWTです。違いは「状態をどこに置くか」の一点で、そこから性質の差が全部出てきます。

今回も手を動かしました。署名を確かめない実装を書いて、admin を true に書き換えたトークンを投げたらどうなるかを試しています。結果を先に言うと、そのまま管理者として受理されました。そこから、両者の選び方までを見ていきます。

この記事の要点

  • 違いは状態をどこに置くか。セッションはサーバー側、JWTは利用者側
  • JWTは署名を確かめて初めて意味を持つ。中身は誰でも読めるし書き換えられる
  • 実測では、署名を見ない実装がadmin=true の偽造トークンを受理した
  • 即座の失効が要るならセッション。JWTは期限が切れるまで有効であり続ける

セッションとJWTは何がどう違うのか

違いは1つだけで、ログイン状態をどこに置くかです。セッションはサーバー側に、JWTは利用者側に置きます。ここから使い勝手の差が全部出てきます。

セッションとJWTを比べる前に、なぜこの仕組みが要るのかを押さえておきます。Webの通信は、1回ごとに独立しているという前提で作られているからです。

つまりサーバーから見ると、さっきログインした人と、いま画面を開いた人が同じかどうかは分かりません。そこで「あなたはさっきログインした人です」と示す仕組みが要ります。それがセッションであり、JWTです。

セッションは、サーバーに記録を残す

セッションでは、ログインした時点でサーバー側に記録を作ります。利用者が持つのは、その記録を指す番号(セッションID)だけです。次に来たときは、その番号でサーバーの記録を引きます。

JWTは、利用者にトークンを持たせる

JWTでは、サーバーに記録を残しません。代わりに「この人はaliceで、管理者ではない」といった内容を書いたトークンを渡し、利用者に持ってもらいます。次に来たときは、そのトークンの中身を読みます。

ここで当然の疑問が出ます。持ち主が書き換えたらどうなるのか、です。それを防ぐのが署名で、この記事の後半はまるごとその話になります。

セッションJWT
状態の置き場所サーバー側利用者側
利用者が持つもの記録を指す番号だけ中身の入ったトークン
毎回の確認方法サーバーの記録を引く署名を検証する
中身を読めるか読めない(番号のみ)誰でも読める
即座に無効化できる(記録を消す)できない(期限まで有効)
サーバーの負荷記録の保管が要る保管は不要
表1 ── セッションとJWTの性質の違い(出典: OWASP Session Management Cheat Sheet / RFC 8725、2026-08-17取得)
余談 新しい方が優れている、という話ではない

JWTの方が新しい技術なので優れている、と受け取られることがあります。ただ両者は用途が違うだけです。複数のサービスにまたがる構成ではJWTが向き、単一のWebアプリならセッションの方が簡単に済みます。この記事の最後で、選び方をもう一度整理します。

出典OWASP Cheat Sheet Series「Session Management」2026-08-17 確認
HTTP is a stateless protocol ( RFC2616 section 5), where each request and response pair is independent of other web interactions
原文OWASP Cheat Sheet Series「Session Management」 この内容の有効期限2027-02-17

署名を確かめない実装に、admin=true を投げてみた

署名の検証を省いた実装を書いて、権限を書き換えたトークンを投げました。そのまま管理者として受理されます。期限切れのトークンも通りました。

セッションとJWTの安全性の差は、実装の手数から生まれます。手元で試したので、その結果を見てください。

用意したのは、JWTを受け取る関数を2つです。片方は中身を読むだけ、もう片方は署名とアルゴリズムと期限を確かめます。下のコードは読み飛ばして大丈夫です。

javascript
// 危険: 中身を読むだけ。署名も期限も見ていない
function verifyUnsafe(token) {
  const [, body] = token.split('.');
  return JSON.parse(Buffer.from(body, 'base64url').toString());
}

// 安全: アルゴリズムを固定し、署名と期限の両方を確かめる
function verifySafe(token) {
  const [h, b, sig] = token.split('.');
  const header = JSON.parse(Buffer.from(h, 'base64url').toString());
  if (header.alg !== 'HS256') return { error: 'alg が想定と違う' };
  const expected = crypto.createHmac('sha256', SECRET).update(`${h}.${b}`).digest('base64url');
  if (sig !== expected) return { error: '署名が一致しない' };
  const payload = JSON.parse(Buffer.from(b, 'base64url').toString());
  if (payload.exp * 1000 < Date.now()) return { error: '有効期限切れ' };
  return payload;
}
text
--- 正規のトークン ---
  検証なしの実装: 受理 user=alice admin=false
  検証ありの実装: 受理 user=alice admin=false
--- 攻撃1: 署名を外し、admin を true に書き換えた(alg:none)---
  検証なしの実装: 受理 user=alice admin=true
  検証ありの実装: 拒否(alg が想定と違う)
--- 攻撃2: 期限切れの正規トークンを再利用 ---
  検証なしの実装: 受理 user=alice admin=false
  検証ありの実装: 拒否(有効期限切れ)

通りました。admin=true です。署名は付けていません。トークンの中身を書き換えて、署名の欄を空にしただけです。

なぜ署名なしで通ってしまうのか

JWTはヘッダに「どの方式で署名したか」を書きます。そこに none(署名なし)と書けてしまうのが問題です。RFCは、攻撃者がこの値を none に変えると、一部のライブラリはそれを信じて署名を確かめずに通してしまう、と明記しています。

つまりヘッダの値を信用してはいけないということです。受け取る側が「うちはHS256しか受け付けない」と決めておけば、この攻撃は成立しません。上の安全な実装がやっているのがそれです。

期限切れのトークンも通っていた

もう1つの結果も見てください。期限が切れた正規のトークンも、検証なしの実装は受理しています。署名は正しいので、期限を見ていなければ何年前のトークンでも通ります。

JWTの検証は3つそろって初めて成立する。1つでも欠けると、その分だけ穴が開く。

充足 2 / 3アルゴリズムを固定しているヘッダの値を信用するとalg:noneが通る署名が一致することを確かめている中身を読むだけでは改ざんを検出できない有効期限を確かめている署名が正しくても期限切れは拒否する必要がある3つ目が抜けやすい。署名の検証だけで安心してしまい、期限の確認を忘れる実装が多い。
図1 ── JWTを受け取るときに確かめること。1つでも欠けると通してはいけないものが通る

入力を信用しない、という点ではSQLインジェクションの記事と同じ構図です。あちらは入力がSQLの命令として読まれる問題で、こちらは受け取ったトークンの自己申告を信じてしまう問題です。

ライブラリを使っていても、設定次第で同じことが起きる実務では自前で検証を書くことは少なく、ライブラリを使うはずです。ただしアルゴリズムを指定せずに検証する関数を呼ぶと、ヘッダの値が使われる場合があります。使っているライブラリで、受け入れるアルゴリズムを明示的に指定できているかを確認してください。
出典RFC 8725「JSON Web Token Best Current Practices」2026-08-17 確認
The algorithm can be changed to "none" by an attacker, and some libraries would trust this value and "validate" the JWT without checking any signature.
原文RFC 8725「JSON Web Token Best Current Practices」 この内容の有効期限2027-02-17

自分のアプリではどちらを選べばいいのか

判断の分かれ目は、すぐに失効させたい場面があるかどうかです。あるならセッション。無いか、複数サービスにまたがるならJWTが向きます。

セッションとJWTのどちらを選ぶかは、好みではなく構成で決まります。まず、いちばん効く判断基準から見ます。

すぐに失効させたい場面があるか

退会した直後、パスワードを変えた直後、不正が疑われたとき。こうした場面でいますぐログイン状態を切りたいなら、セッションが向きます。サーバー側の記録を消すだけで済むからです。

JWTではこれができません。発行したトークンは期限が来るまで有効です。対処として失効した一覧をサーバーに持つ方法もありますが、それは「サーバーに記録を持たない」というJWTの利点を手放すことになります。

期限は短いほど安全、ただし手間が増える

どちらを使う場合でも、有効期間は短いほど安全です。OWASPも、期間が短いほど攻撃者が使える時間が減ると述べています。ただし短くすると再ログインが増えるので、使い勝手との兼ね合いになります。

失効の必要性が最初の分かれ道。次にサービスが1つかどうかで決まる。

すぐに失効させたい場面があるかいいえ次の判断へ進むはい扱うサービスは1つだけかいいえJWTが向く(サービス間で持ち回れる)はいセッションで足りる。実装が簡単で失効も容易1つ目が「はい」ならその時点でセッション。JWTに失効の仕組みを足すと、結局サーバー側に記録を持つことになる。
図2 ── セッションとJWTの選び方

個人開発で作るWebアプリの多くは、サービスが1つで、退会や不正時にすぐ切りたい場面があります。その条件ならセッションで足ります。JWTを選ぶ理由が特に無いなら、実装が簡単な方を選んで構いません。

余談 両方を組み合わせる形もある

短い期限のJWTと、失効できる長期のトークンを組み合わせる構成もよく使われます。普段の通信はJWTで軽く済ませ、期限が切れたら長期トークンで作り直す形です。ただし仕組みが増えるぶん、確認すべき箇所も増えます。最初から選ぶ構成ではないと編集部は考えています。

出典OWASP Cheat Sheet Series「Session Management」2026-08-17 確認
The shorter the session interval is, the lesser the time an attacker has to use the valid session ID.
原文OWASP Cheat Sheet Series「Session Management」 この内容の有効期限2027-02-17

よくある質問

JWTの中身は暗号化されていますか?
されていません。標準的なJWTは署名されているだけで、中身はBase64で符号化されているだけです。誰でも読めるので、パスワードや個人情報をJWTに入れてはいけません。署名が保証するのは「改ざんされていないこと」であって、「読まれないこと」ではありません。
JWTではログアウトできないのですか?
厳密には、発行済みトークンをサーバー側から無効にできません。期限が切れるまで有効なままです。対処としては、有効期限を短くする、失効したトークンの一覧をサーバーに持つ、といった方法がありますが、後者は「状態を持たない」というJWTの利点を手放すことになります。
どちらが安全ですか?
仕組みとしての優劣ではなく、実装で決まります。ただし、セッションは「サーバーの記録を見る」だけなのに対し、JWTは署名の検証・アルゴリズムの固定・期限の確認を自分で正しく行う必要があり、間違えられる箇所が多くなります。
スマホアプリならJWTがよいと聞きました
複数のサービスにまたがる場合や、Cookieを使いにくい環境ではJWTが向きます。一方で単一のWebアプリなら、セッションの方が扱いは簡単です。構成の都合で選ぶもので、新しい方が優れているという話ではありません。

まとめ

  • セッションとJWTの違いは状態をどこに置くかだけ。そこから性質の差が出る
  • JWTは署名の検証が全て。実測では検証しない実装が偽造トークンを受理した
  • 即座に失効させたいならセッション。JWTは期限まで有効であり続ける
  • 選ぶ基準は構成の都合。単一のWebアプリならセッションで足りる

今日から始められること

  1. JWTを使っている箇所で、署名の検証を行っているか確認する
  2. 検証時にアルゴリズムを固定しているか確認する(ヘッダの値を信用しない)
  3. 有効期限の確認が入っているか確認する
  4. JWTの中身に、他人に読まれて困る情報が入っていないか確認する

実務で組んだセッションとJWTのワークフローには、値段が付きます

同じ課題を持つ会社にとって、動いている設定は「作る時間」を買えるということです。ServiceDockは自作のワークフローやテンプレートを出品できるマーケットプレイスです。手数料や出品の流れは出品者向けページにまとまっています。

出品の仕組みを見る