0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

License System Day 7: JWT(JSON Web Token)入門

Last updated at Posted at 2025-12-06

🎄 科学と神々株式会社 アドベントカレンダー 2025

License System Day 7: JWT(JSON Web Token)入門


📖 今日のテーマ

今日は、ライセンスキーとして使用する**JWT(JSON Web Token)**について学びます。

JWTは現代のWeb認証で最も広く使われている技術の一つです。その仕組みと利点を理解しましょう。


🎫 JWT とは?

定義

JWT = JSON Web Token
(ジェイソン・ウェブ・トークン / ジョット)

JSON形式のデータを安全に送受信するための標準規格(RFC 7519)

見た目

eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiMTIzNDU2Nzg5MCIsInBsYW4iOiJwcmVtaXVtIiwiaWF0IjoxNjk5NTY0ODAwLCJleHAiOjE3MzExMDA4MDB9.MEUCIQDz7vkKYYjPxQw3vR2S8tYLGF9QrX1K3N5WpZzJxGqA-QIgbHZm3pL8dE2fR7tQ4sV9uW0xY1zA2bC3dD4eE5fF6g

3つのパートが . で区切られている!


🏗️ JWT の構造

3つのパート

JWT = Header.Payload.Signature

1. Header(ヘッダー)
   ↓
2. Payload(ペイロード)
   ↓
3. Signature(署名)

1. Header(ヘッダー)

{
  "alg": "ES256",
  "typ": "JWT"
}
alg: 署名アルゴリズム
  - ES256 = ECDSA P-256 + SHA256
  - RS256 = RSA + SHA256
  - HS256 = HMAC + SHA256

typ: トークンタイプ
  - JWT(固定)

Base64 URL エンコード後:

eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9

2. Payload(ペイロード)

{
  "user_id": "1234567890",
  "plan": "premium",
  "iat": 1699564800,
  "exp": 1731100800
}
標準クレーム:
  iss: 発行者(Issuer)
  sub: 主体(Subject)
  aud: 対象者(Audience)
  exp: 有効期限(Expiration Time)
  iat: 発行時刻(Issued At)

カスタムクレーム:
  user_id: ユーザーID
  plan: プラン種別
  features: 利用可能機能

Base64 URL エンコード後:

eyJ1c2VyX2lkIjoiMTIzNDU2Nzg5MCIsInBsYW4iOiJwcmVtaXVtIiwiaWF0IjoxNjk5NTY0ODAwLCJleHAiOjE3MzExMDA4MDB9

3. Signature(署名)

署名 = ECDSA(
  Base64URL(Header) + "." + Base64URL(Payload),
  秘密鍵
)

署名の目的:

  • 改ざん検知
  • 発行者の確認

例:

MEUCIQDz7vkKYYjPxQw3vR2S8tYLGF9QrX1K3N5WpZzJxGqA-QIgbHZm3pL8dE2fR7tQ4sV9uW0xY1zA2bC3dD4eE5fF6g

🔨 JWT の作成(サーバー側)

Node.js での実装

const jwt = require('jsonwebtoken');
const fs = require('fs');

// 秘密鍵の読み込み
const privateKey = fs.readFileSync('./keys/private_key.pem', 'utf8');

// ペイロードの定義
const payload = {
  user_id: '1234567890',
  email: 'user@example.com',
  plan: 'premium',
  features: {
    echo: true,
    advancedEcho: true,
    bulkOperation: true
  }
};

// JWT の生成
const token = jwt.sign(payload, privateKey, {
  algorithm: 'ES256',        // ECDSA P-256
  expiresIn: '365d',        // 365日間有効
  issuer: 'license-server', // 発行者
  subject: payload.user_id  // 主体(ユーザーID)
});

console.log('JWT:', token);

🔍 JWT の検証(クライアント側)

Node.js での実装

const jwt = require('jsonwebtoken');
const fs = require('fs');

// 公開鍵の読み込み
const publicKey = fs.readFileSync('./keys/public_key.pem', 'utf8');

// JWT の検証
try {
  const decoded = jwt.verify(token, publicKey, {
    algorithms: ['ES256'],
    issuer: 'license-server'
  });

  console.log('✅ 検証成功!');
  console.log('ユーザーID:', decoded.user_id);
  console.log('プラン:', decoded.plan);
  console.log('有効期限:', new Date(decoded.exp * 1000));

} catch (error) {
  if (error.name === 'TokenExpiredError') {
    console.log('❌ トークンの有効期限が切れています');
  } else if (error.name === 'JsonWebTokenError') {
    console.log('❌ トークンが不正です');
  } else {
    console.log('❌ 検証エラー:', error.message);
  }
}

✅ JWT の利点

1. Stateless(状態を持たない)

従来のセッション管理:
  ┌──────────┐           ┌──────────┐
  │クライアント│           │サーバー   │
  └─────┬────┘           └────┬─────┘
        │                     │
        │ 1. ログイン          │
        ├─────────────────────►
        │                     │
        │ 2. セッションID      │
        │◄─────────────────────┤
        │                     │
        │                   【メモリ】
        │                   セッション情報:
        │                   - user_id: 123
        │                   - plan: premium
        │
        │ 3. リクエスト        │
        │ + セッションID       │
        ├─────────────────────►
        │                     │
        │                   【DB検索】
        │                   セッション情報を取得
        │
        │ 4. レスポンス        │
        │◄─────────────────────┤

問題:
  - サーバーにセッション情報を保持
  - スケールしにくい
  - メモリ/DB負荷
JWT:
  ┌──────────┐           ┌──────────┐
  │クライアント│           │サーバー   │
  └─────┬────┘           └────┬─────┘
        │                     │
        │ 1. ログイン          │
        ├─────────────────────►
        │                     │
        │ 2. JWT              │
        │◄─────────────────────┤
        │  (全情報を含む)     │
        │                     │
        │                   【保存不要】
        │
        │ 3. リクエスト        │
        │ + JWT               │
        ├─────────────────────►
        │                     │
        │                   【署名検証のみ】
        │                   DB アクセス不要!
        │
        │ 4. レスポンス        │
        │◄─────────────────────┤

利点:
  - サーバーは状態を持たない
  - 水平スケーリングが容易
  - DB負荷が減る

2. 自己完結型

JWT に含まれる情報:
  ✅ ユーザーID
  ✅ プラン情報
  ✅ 権限
  ✅ 有効期限

→ DB を見なくても判断できる!

3. クロスドメイン対応

同じ JWT で複数のサービスにアクセス:

api.example.com  ←─┐
                   │
cdn.example.com  ←─┼─ 同じJWTで認証
                   │
admin.example.com ←┘

→ SSO(シングルサインオン)が簡単!

⚠️ JWT の注意点

1. ペイロードは暗号化されていない

❌ 危険な使い方:
{
  "user_id": "123",
  "password": "secret123",  ← NG!
  "credit_card": "1234-5678-..." ← NG!
}

✅ 正しい使い方:
{
  "user_id": "123",
  "plan": "premium",
  "features": ["echo", "advanced"]
}

→ 機密情報は入れない!

2. トークンの無効化が難しい

問題: ユーザーがログアウトしても...

JWT は有効期限まで使える

対策:
  1. 短い有効期限(例: 1時間)
  2. リフレッシュトークンの併用
  3. ブラックリストの管理

3. サイズが大きくなりがち

セッションID:  16-32 bytes
JWT:           200-500 bytes

→ 毎回のリクエストで送信
→ 帯域幅の消費が増える

対策:
  - 必要最小限の情報のみ
  - 圧縮の検討

🔄 リフレッシュトークンパターン

仕組み

アクセストークン(短命):
  有効期限: 15分
  用途: API アクセス

リフレッシュトークン(長命):
  有効期限: 30日
  用途: アクセストークンの再発行

フロー:
  1. ログイン
     → アクセストークン + リフレッシュトークン

  2. API アクセス(15分以内)
     → アクセストークンで認証

  3. アクセストークン期限切れ
     → リフレッシュトークンで再発行

  4. 新しいアクセストークン取得
     → 引き続き API アクセス

  5. ログアウト
     → リフレッシュトークンを無効化

🌟 まとめ

今日学んだこと:

  1. JWT の構造

    • Header.Payload.Signature
    • Base64 URL エンコード
  2. JWT の作成と検証

    • jwt.sign() で作成
    • jwt.verify() で検証
  3. JWT の利点

    • Stateless
    • 自己完結型
    • クロスドメイン対応
  4. 注意点

    • 機密情報は入れない
    • 無効化が難しい
    • サイズに注意
  5. ベストプラクティス

    • 短い有効期限
    • リフレッシュトークンの併用
    • HTTPS 必須

🎓 理解度チェック

  1. JWT の3つのパートは?
  2. Payload は暗号化されている?
  3. Stateless の利点は?
  4. なぜ機密情報を入れてはいけない?

💡 次回予告

Day 8: 署名検証の実装

  • サーバー側での署名生成
  • クライアント側での検証
  • エラーハンドリング
  • セキュリティベストプラクティス

お楽しみに!


前回: Day 6: ECDSA P-256署名の仕組み
次回: Day 8: 署名検証の実装

Happy Learning! 🎉

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?