auth

Updated: 10/18/2025, 5:40:23 PM

JWT認証システム実装ガイド 第一回 - 本質を理解して堅牢な認証を構築する

10/18/2025, 5:40:23 PM

1. はじめに

この記事で学べること

この記事では、JWT(JSON Web Token)を使った認証システムの実装について、実際にプロダクションで動くシステムを構築した経験をもとに解説します。

単なるチュートリアルではなく、以下のような「現場で必要になる知識」を重点的に扱います:

  • JWTの本質的な理解(「認証フレームワーク」という誤解を解く)
  • シーケンス図で理解する認証フロー全体像
  • Web/モバイル両対応のトークン管理戦略
  • HttpOnly Cookieの理想と現実
  • 実装時によくハマる落とし穴とその回避方法

想定読者

以下のような方を想定しています:

  • バックエンドAPIを開発している(またはこれから開発する)
  • 「JWTって何となく使ってるけど、本質は理解してない」
  • モバイルアプリ対応の認証システムを作りたい
  • セッションベース認証からの移行を検討している
  • セキュアな認証システムの設計方法を知りたい

前提知識

以下の知識があるとスムーズに読めます(必須ではありません):

  • HTTP/HTTPSの基本
  • REST APIの概念
  • JSON形式の理解
  • 何らかのバックエンド言語の経験(Go、Node.js、Python など)
  • Redisなどのキャッシュストアの基本的な使い方

言語非依存の内容なので、特定のフレームワークに縛られず応用できます。


2. JWT認証の本質を理解する

2.1 JWTとは何か

JWTは「認証フレームワーク」ではない

まず最初に重要な認識を正しましょう。

JWTは認証システムそのものではありません。

よくある誤解:

❌ 「JWT認証を使えば認証システムが作れる」
❌ 「JWTはセッション管理の仕組み」
❌ 「JWTがあれば安全な認証ができる」

正しい理解:

✅ JWTは「署名付き自己完結型トークン」のフォーマット仕様
✅ 認証システムの「部品」として使うもの
✅ JWTだけでは認証システムは完成しない

例えるなら:JWTは「南京錠」のようなもの。鍵をかけて本人確認はできるけど、「誰に鍵を渡すか」「鍵をどう管理するか」は別途設計が必要です。

署名付き自己完結型トークンという正体

JWTの本質は 「改ざんできない情報の入れ物」 です。

通常のJSONデータとの違い:

// 普通のJSON(誰でも書き換え可能) { "user_id": 123, "email": "user@example.com" }
// JWT(署名付きなので改ざん検知可能)
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJ1c2VyX2lkIjoxMjMsImVtYWlsIjoidXNlckBleGFtcGxlLmNvbSJ9.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

この 「改ざんできない」 特性が、JWTの最大の価値です。

JWTの構造(Header, Payload, Signature)

JWTは3つの部分から構成されます:

[Header].[Payload].[Signature]

1. Header(ヘッダー)

{ "alg": "HS256", // 署名アルゴリズム "typ": "JWT" // トークンタイプ }

→ Base64エンコード → eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

2. Payload(ペイロード)

{ "user_id": 123, "email": "user@example.com", "exp": 1697545200 // 有効期限(UNIXタイムスタンプ) }

→ Base64エンコード → eyJ1c2VyX2lkIjoxMjMsIm...

3. Signature(署名)

HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret_key
)

→ 署名結果 → SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

重要なポイント:

  • Header と Payload は 暗号化されていない(Base64は暗号化ではない)
  • 誰でも中身を読める(jwt.ioで確認可能)
  • 改ざんすると Signature が一致しなくなる → これが本質

つまり、JWTは 「秘密にする」ためではなく「改ざんを防ぐ」ためのもの です。


2.2 JWT認証の核心

本質:アクセストークンで本人確認、リフレッシュトークンで更新

JWT認証システムの本質は、驚くほどシンプルです:

1. アクセストークン(JWT)で本人確認する
2. 期限切れになったらリフレッシュトークンで更新する

以上。

それだけ? はい、本当にそれだけです。

複雑に見えるのは、この2つのトークンを 「どう発行するか」「どう保存するか」「どう無効化するか」 という周辺設計の話です。

なぜ2種類のトークンが必要なのか

Q: アクセストークンだけじゃダメなの?

ダメです。理由は2つあります:

理由1: セキュリティ vs 利便性のトレードオフ

トークンの有効期限セキュリティユーザー体験
5分⭐⭐⭐⭐⭐❌ 頻繁にログアウトされる
30日❌ 盗まれたら30日間悪用可能⭐⭐⭐⭐⭐

理由2: 即座にログアウトできない問題

JWTは自己完結型なので、サーバー側で「無効化」できません:

ユーザー: 「ログアウトしたい」
サーバー: 「了解!」

でも実際は...
→ アクセストークンはまだ有効(最大30日間使える)
→ 本当の意味でのログアウトができていない

解決策:2トークン方式

アクセストークン(短命・15分)
  ↓
  盗まれても被害は15分間だけ
  ログアウト時は放置してOK(15分で自然消滅)

リフレッシュトークン(長命・30日)
  ↓
  Redis等で管理(削除可能)
  ログアウト時に削除すれば即座に無効化

これにより 「セキュリティと利便性を両立」 できます。

ステートレス vs ステートフル

JWT認証の特徴を理解するには、この2つの概念が重要です:

ステートレス(Stateless)

サーバー側で状態を保持しない方式

例:アクセストークン(JWT)
  - サーバーは「この人がログインしてる」を記録しない
  - トークンの署名を検証するだけ
  - 超高速(データベース・Redis不要)
  - スケールしやすい(どのサーバーでも検証可能)

問題点:
  - 即座に無効化できない
  - 発行したら取り消せない

ステートフル(Stateful)

サーバー側で状態を保持する方式

例:リフレッシュトークン(UUID)
  - Redisに「このトークンは有効」と記録
  - リクエストごとにRedisをチェック
  - 削除すれば即座に無効化

問題点:
  - Redisアクセスが必要(わずかに遅い)
  - Redisが落ちると認証不可

ハイブリッド戦略:両方のいいとこ取り

通常のAPIリクエスト
  → アクセストークン(JWT)で超高速認証

トークン更新・ログアウト
  → リフレッシュトークン(Redis)で確実に制御

この組み合わせが、モダンなJWT認証システムの定石です。


2.3 他の認証方式との比較

JWT認証は万能ではありません。他の認証方式と比較して、適切な選択をしましょう。

セッションベース認証との違い

セッションベース認証(従来型)

1. ログイン
   ↓
2. サーバーがセッションIDを発行
   ↓
3. セッションIDをCookieに保存
   ↓
4. リクエストごとにセッションIDで照合

仕組み:

クライアント: 「セッションID: abc123」
サーバー: 「データベース見るか... OK、user_id=123だな」

比較表:

項目セッションベースJWT認証
状態管理サーバー側クライアント側
スケーラビリティ⭐⭐(セッション同期が必要)⭐⭐⭐⭐⭐(どのサーバーでもOK)
パフォーマンス⭐⭐⭐(DB/Redis必須)⭐⭐⭐⭐⭐(署名検証のみ)
モバイル対応⭐⭐(Cookie依存)⭐⭐⭐⭐⭐(JSON返却)
ログアウト⭐⭐⭐⭐⭐(即座に可能)⭐⭐⭐(工夫が必要)
実装難易度⭐⭐⭐(簡単)⭐⭐⭐⭐(やや複雑)

使い分けの目安:

セッションベースを選ぶべき場合:
✅ Webアプリのみ(モバイル不要)
✅ シンプルな実装を優先
✅ 即座のログアウトが重要
✅ 小規模サービス(スケール不要)

JWT認証を選ぶべき場合:
✅ モバイルアプリ対応が必要
✅ マイクロサービス構成
✅ 高トラフィック・高スケーラビリティ
✅ API提供がメイン

OAuth 2.0との関係

よくある誤解:

❌ 「JWTとOAuthは別物」
❌ 「OAuthを使えばJWTは不要」

正しい理解:

✅ OAuth 2.0は「認可フレームワーク」
✅ JWTはOAuth 2.0の「アクセストークン形式」として使われることが多い
✅ 両者は補完関係

関係性の図:

OAuth 2.0(認可フレームワーク)
  ├─ Authorization Code Flow(認可フロー)
  ├─ Refresh Token(更新トークン)
  └─ Access Token(アクセストークン)← ここでJWTを使う

例:Google OAuth

1. 「Googleでログイン」ボタンをクリック
   ↓(OAuth 2.0の認可フロー)
2. Googleが認証してauthorization_codeを返す
   ↓
3. あなたのサーバーがaccess_tokenを取得
   ↓(ここでJWT形式のトークンを自分で発行)
4. 以降はJWT認証で本人確認

API Keyとの使い分け

API Key(永続的なトークン)

例: api_key_1234567890abcdefghijklmnop

特徴:
- 有効期限なし(手動で削除するまで有効)
- マシン to マシン通信向け
- ユーザーごとではなくアプリケーションごと

JWT(一時的なトークン)

例: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

特徴:
- 有効期限あり(15分〜30分)
- ユーザー to サーバー通信向け
- ユーザーごとに異なる

使い分け:

ユースケース推奨方式
ユーザーログインJWT
サーバー間通信API Key
Webhookの受信API Key
モバイルアプリJWT
CLIツールAPI Key or JWT
サードパーティ連携OAuth 2.0 + JWT

まとめ(第1回)

今回学んだこと

  1. JWTは認証フレームワークではなく「署名付きトークン」

    • 改ざん防止が本質
    • 暗号化ではない(中身は見える)
  2. JWT認証の核心は単純

    • アクセストークン(短命)で本人確認
    • リフレッシュトークン(長命)で更新
    • これだけ!
  3. ステートレス + ステートフル のハイブリッド戦略

    • 高速性とセキュリティを両立
  4. 他の認証方式との使い分けが重要

    • セッション vs JWT
    • OAuth 2.0の中でJWTを使う
    • API Keyとの役割の違い

次回予告

次回は 「シーケンス図で理解する認証フロー」 です。

  • サインアップ・ログイン・ログアウトを独立したフローとして設計する方法
  • 視覚的に理解する5つの認証フロー
  • 実装時に「何をいつ実行するか」が明確になります