Zenn
Open3

認証に入門する

kagomekagome

セッション認証におけるログインプロセス:

  1. ブラウザでログイン情報を送信
  2. サーバがDBでユーザー認証
  3. 認証成功後、サーバはセッションストアに新しいセッションデータを作成・保存
  4. サーバはセッションIDをcookieとしてブラウザに送信
  5. 以降のリクエストでブラウザはcookieを送信
  6. サーバはcookieからセッションIDを取得し、セッションストアで検証
kagomekagome

セッションIDの具体例

セッションIDは通常、ランダムで予測不可能な一意の文字列です。

具体的なセッションIDの例:

2c5ea4c0-4067-11e9-8bad-9b1deb4d3b7d   // UUID形式
sess:a7bfd23e5b1d6c293a5d5c3b3c0e5a9f   // 16進数文字列
XpQW8c3DZDCPywEU9jYsWNfz7DYxn5VS       // ランダムな英数字

実際のHTTPリクエスト/レスポンスでの例:

サーバーからのレスポンスヘッダー (ログイン成功時):

Set-Cookie: sessionId=XpQW8c3DZDCPywEU9jYsWNfz7DYxn5VS; Path=/; HttpOnly; Secure; SameSite=Strict; Max-Age=3600

クライアントからのリクエストヘッダー (以降のリクエスト):

Cookie: sessionId=XpQW8c3DZDCPywEU9jYsWNfz7DYxn5VS

セッションデータの具体例

セッションデータは、セッションストアに保存される情報で、様々な形式があります。

Redis内のセッションデータ例 (JSON形式):

{
  "userId": 12345,
  "username": "tanaka_taro",
  "email": "tanaka@example.com",
  "isAuthenticated": true,
  "roles": ["user", "editor"],
  "permissions": ["read", "write", "edit"],
  "lastActivity": 1623485621,
  "createdAt": 1623482021,
  "expiresAt": 1623489621,
  "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...",
  "ipAddress": "192.168.1.100",
  "cart": {
    "items": [
      { "productId": 101, "quantity": 2, "price": 1200 },
      { "productId": 205, "quantity": 1, "price": 3500 }
    ],
    "total": 5900
  },
  "preferences": {
    "language": "ja",
    "theme": "dark"
  }
}

PostgreSQL等のリレーショナルDBでのセッションテーブル構造例:

CREATE TABLE sessions (
  session_id VARCHAR(128) PRIMARY KEY,
  user_id INTEGER REFERENCES users(id),
  data JSONB,
  ip_address VARCHAR(45),
  user_agent TEXT,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  expires_at TIMESTAMP
);

データ例:

session_id: "XpQW8c3DZDCPywEU9jYsWNfz7DYxn5VS"
user_id: 12345
data: {"isAuthenticated": true, "roles": ["user"], "cart": {"items": [...]}}
ip_address: "192.168.1.100"
user_agent: "Mozilla/5.0..."
created_at: "2023-06-12 14:27:01"
updated_at: "2023-06-12 14:42:15"
expires_at: "2023-06-12 15:27:01"

Express.jsなどのNode.jsフレームワークでのセッションオブジェクト例:

req.session = {
  userId: 12345,
  username: 'tanaka_taro',
  authenticated: true,
  lastVisit: Date.now(),
  views: 27,
  cart: {
    items: [/* カート内アイテム */],
    total: 5900
  }
};

セッションストアの実装方法による違い

セッションデータの保存方法はフレームワークやライブラリによって異なります:

  1. Redisの場合 - キーバリュー形式で保存:

    KEY: "sess:XpQW8c3DZDCPywEU9jYsWNfz7DYxn5VS"
    VALUE: "{\"userId\":12345,\"username\":\"tanaka_taro\",\"isAuthenticated\":true,...}"
    
  2. ファイルシステムの場合 - ファイルとして保存:

    /tmp/sessions/sess_XpQW8c3DZDCPywEU9jYsWNfz7DYxn5VS
    

    ファイル内容は通常シリアライズ(JSON, PHPのserializeなど)されたデータ

  3. メモリ内の場合 - アプリケーションメモリ内のMap/Dictionary:

    sessions = {
      "XpQW8c3DZDCPywEU9jYsWNfz7DYxn5VS": {
        userId: 12345,
        // その他のデータ
      }
    }
    

セッションデータには様々な情報が含まれますが、一般的に最低限ユーザーIDと認証状態が含まれるため、サーバー側で特定のユーザーに関連付けられたデータを維持できます。

kagomekagome

トークン認証のプロセス

  1. ブラウザがログイン情報を送信

    • ユーザーがメールアドレスとパスワードを入力し送信
    • POST /api/login { "email": "user@example.com", "password": "secret123" }
  2. サーバーがDBでユーザー認証

    • データベースでユーザー情報を検索し、パスワードを検証
    • 認証失敗時はエラーレスポンスを返す
  3. 認証成功後、サーバーはJWTトークンを生成

    • ユーザーIDや権限などの情報をペイロードに含める
    • 秘密鍵を使用してトークンに署名
    • セッション認証との違い: セッションストアに情報を保存しない
    const payload = {
      sub: user.id,             // ユーザーID
      name: user.name,          // ユーザー名
      roles: user.roles,        // 権限
      iat: Date.now() / 1000,   // 発行時間
      exp: (Date.now() / 1000) + 3600 // 有効期限(1時間)
    };
    const token = jwt.sign(payload, SECRET_KEY);
    
  4. サーバーはJWTトークンをクライアントに送信

    • レスポンスボディで返す場合
      { "token": "eyJhbGciOiJIUzI1NiIsI..." }
      
    • または、HTTPOnly Cookieとして設定
      Set-Cookie: token=eyJhbGciOiJIUzI1NiIsI...; HttpOnly; Secure; SameSite=Strict
      
  5. 以降のリクエストでブラウザはトークンを送信

    • Authorizationヘッダーで送信する場合
      Authorization: Bearer eyJhbGciOiJIUzI1NiIsI...
      
    • またはCookieを自動的に送信(Cookieに保存した場合)
  6. サーバーはトークンを検証

    • トークンの署名を検証(秘密鍵を使用)
    • トークンの有効期限を確認
    • セッション認証との違い: データベースやセッションストアへの問い合わせは不要
    try {
      const decoded = jwt.verify(token, SECRET_KEY);
      // decoded にはペイロードの内容が含まれる
      const userId = decoded.sub;
      const userRoles = decoded.roles;
      
      // トークンの有効期限をチェック
      if (decoded.exp < Date.now() / 1000) {
        return res.status(401).json({ error: 'トークンの有効期限切れ' });
      }
      
      // リクエスト処理を続行...
    } catch (err) {
      return res.status(401).json({ error: 'トークンが無効です' });
    }
    
  7. 検証成功後、サーバーはリクエストを処理

    • トークンから抽出したユーザー情報を使用
    • 必要に応じて、データベースから追加情報を取得

セッション認証とトークン認証の主な違い(処理フロー)

ステップ セッション認証 トークン認証
認証成功後のサーバー処理 セッションストアにデータ保存 JWTトークン生成(署名)
クライアントへの送信 セッションIDをCookieに設定 JWTをレスポンスボディまたはCookieで送信
リクエスト検証 セッションIDを元にセッションストア検索 トークンの署名を検証(計算処理のみ)
ユーザー情報の取得 セッションストアから取得 トークン自体から取得(デコード)
サーバー側の状態 あり(セッションデータ) なし(ステートレス)

トークン認証の実装バリエーション

1. トークン保存場所

  • ローカルストレージ/セッションストレージ
  • HTTPOnly Cookie
  • メモリ内保存(SPAのJavaScript変数)

2. リフレッシュトークンの使用

1. ログイン時にアクセストークン+リフレッシュトークンを発行
2. アクセストークン期限切れ時、リフレッシュトークンで新アクセストークン取得
3. リフレッシュトークンはサーバー側DBに保存(無効化可能)

3. トークン無効化の仕組み

1. トークンをブラックリスト(Redis等)に登録
2. リクエスト時にトークンがブラックリストにないか確認

トークン認証は特にAPIベースのアーキテクチャ、SPAアプリケーション、マイクロサービスなどで広く採用されています。

ログインするとコメントできます