認証API フロー図(エラー処理含む) - nyg1971/business_api GitHub Wiki

認証API フロー図(エラー処理含む)

🎯 全体フロー概要

[フロントエンド] ←→ [Rails API] ←→ [PostgreSQL]
                         ↓
                   [JWT認証システム]

🔐 1. サインアップフロー

[ユーザー入力]
     ↓
┌─────────────────────────────────────────┐
│ POST /api/v1/auth/signup                │
│ {                                       │
│   "user": {                            │
│     "email": "[email protected]",       │
│     "password": "password123",         │
│     "password_confirmation": "...",    │
│     "role": "staff"                    │
│   }                                    │
│ }                                      │
└─────────────────────────────────────────┘
     ↓
┌─────────────────────────────────────────┐
│ AuthController#signup                   │
│                                         │
│ 1. user = User.new(user_params)        │
│ 2. if user.save                        │
└─────────────────────────────────────────┘
     ↓
    【分岐】
     ↓
┌─────────────────────────────────────────┐ ┌─────────────────────────────────────────┐
│ ✅ 成功パス                              │ │ ❌ 失敗パス                              │
│                                         │ │                                         │
│ 3. token = JsonWebToken.encode(...)     │ │ 3. バリデーションエラー                   │
│ 4. render json: {                      │ │ 4. render json: {                      │
│      token: token,                     │ │      errors: user.errors.full_messages │
│      user: user.as_json(...),          │ │    }                                   │
│      expires_at: 24.hours.from_now     │ │ 5. status: :unprocessable_entity       │
│    }                                   │ │    (HTTP 422)                          │
│ 5. status: :created (HTTP 201)         │ │                                         │
└─────────────────────────────────────────┘ └─────────────────────────────────────────┘
     ↓                                       ↓
┌─────────────────────────────────────────┐ ┌─────────────────────────────────────────┐
│ 📱 フロントエンド受信                     │ │ 📱 フロントエンド受信                     │
│                                         │ │                                         │
│ - JWTトークンをローカルストレージに保存    │ │ - エラーメッセージを表示                  │
│ - ユーザー情報を状態管理に保存            │ │ - フォームにエラー表示                   │
│ - ダッシュボードへリダイレクト            │ │ - ユーザーに修正を促す                   │
└─────────────────────────────────────────┘ └─────────────────────────────────────────┘

🚨 よくあるサインアップエラー

エラー内容 原因 HTTPステータス レスポンス例
Email重複 既存ユーザー 422 {"errors": ["Email has already been taken"]}
パスワード短い Devise設定 422 {"errors": ["Password is too short"]}
パスワード不一致 確認入力ミス 422 {"errors": ["Password confirmation doesn't match"]}
Email形式不正 バリデーション 422 {"errors": ["Email is invalid"]}

🔑 2. ログインフロー

[ユーザー入力]
     ↓
┌─────────────────────────────────────────┐
│ POST /api/v1/auth/login                 │
│ {                                       │
│   "email": "[email protected]",         │
│   "password": "password123"            │
│ }                                      │
└─────────────────────────────────────────┘
     ↓
┌─────────────────────────────────────────┐
│ AuthController#login                    │
│                                         │
│ 1. user = User.find_by(email: ...)     │
│ 2. if user&.valid_password?(...)       │
└─────────────────────────────────────────┘
     ↓
    【分岐】
     ↓
┌─────────────────────────────────────────┐ ┌─────────────────────────────────────────┐
│ ✅ 認証成功                              │ │ ❌ 認証失敗                              │
│                                         │ │                                         │
│ 3. token = JsonWebToken.encode(...)     │ │ 3. render json: {                      │
│ 4. render json: {                      │ │      error: 'Invalid credentials'     │
│      token: token,                     │ │    }                                   │
│      user: user.as_json(...),          │ │ 4. status: :unauthorized               │
│      expires_at: 24.hours.from_now     │ │    (HTTP 401)                          │
│    }                                   │ │                                         │
│ 5. status: :ok (HTTP 200)              │ │                                         │
└─────────────────────────────────────────┘ └─────────────────────────────────────────┘
     ↓                                       ↓
┌─────────────────────────────────────────┐ ┌─────────────────────────────────────────┐
│ 📱 フロントエンド受信                     │ │ 📱 フロントエンド受信                     │
│                                         │ │                                         │
│ - JWTトークンをローカルストレージに保存    │ │ - エラーメッセージを表示                  │
│ - ユーザー情報を状態管理に保存            │ │ - ログインフォームにエラー表示            │
│ - ダッシュボードへリダイレクト            │ │ - パスワード欄をクリア                   │
└─────────────────────────────────────────┘ └─────────────────────────────────────────┘

🚨 ログインエラーパターン

エラー内容 原因 処理
ユーザー未存在 User.find_by(email: ...) が nil 401エラー
パスワード間違い valid_password? が false 401エラー
アカウント無効 Deviseの確認待ち等 401エラー

🔒 3. 認証必須API呼び出しフロー

[フロントエンド]
     ↓
┌─────────────────────────────────────────┐
│ GET /api/v1/auth/me                     │
│ Headers:                                │
│   Authorization: Bearer eyJhbG...       │
└─────────────────────────────────────────┘
     ↓
┌─────────────────────────────────────────┐
│ Base Controller#authenticate_request    │
│ (before_action自動実行)                  │
│                                         │
│ 1. header = request.headers['Auth...']  │
│ 2. token = header.split(' ').last      │
│ 3. decoded = JsonWebToken.decode(token) │
│ 4. @current_user = User.find(...)      │
└─────────────────────────────────────────┘
     ↓
    【分岐】
     ↓
┌─────────────────────────────────────────┐ ┌─────────────────────────────────────────┐
│ ✅ 認証成功                              │ │ ❌ 認証失敗                              │
│                                         │ │                                         │
│ 5. AuthController#me 実行               │ │ 5. rescue JWT::DecodeError             │
│ 6. render json: {                      │ │ 6. render_unauthorized                 │
│      user: current_user.as_json(...)   │ │ 7. render json: {                      │
│    }                                   │ │      error: 'Unauthorized'            │
│ 7. status: :ok (HTTP 200)              │ │    }                                   │
│                                         │ │ 8. status: :unauthorized (HTTP 401)   │
└─────────────────────────────────────────┘ └─────────────────────────────────────────┘
     ↓                                       ↓
┌─────────────────────────────────────────┐ ┌─────────────────────────────────────────┐
│ 📱 フロントエンド受信                     │ │ 📱 フロントエンド受信                     │
│                                         │ │                                         │
│ - ユーザー情報を表示                     │ │ - ログアウト処理                        │
│ - ダッシュボード更新                     │ │ - ログインページへリダイレクト            │
│ - 他のAPI呼び出し続行                    │ │ - トークンをローカルストレージから削除    │
└─────────────────────────────────────────┘ └─────────────────────────────────────────┘

🚨 JWT認証エラーパターン

エラー内容 原因 HTTPステータス 対処法
トークンなし Authorizationヘッダー未設定 401 ログイン画面へ
トークン形式不正 Bearer以外、分割失敗 401 ログイン画面へ
トークン期限切れ 24時間経過 401 再ログイン要求
トークン改ざん 署名検証失敗 401 ログイン画面へ
ユーザー削除済み User.find失敗 401 ログイン画面へ

🔄 4. 実際の開発フロー例

📱 フロントエンド側の実装例

// サインアップ
async function signup(userData) {
  try {
    const response = await fetch('/api/v1/auth/signup', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ user: userData })
    });
    
    if (response.ok) {
      const data = await response.json();
      localStorage.setItem('token', data.token);
      localStorage.setItem('user', JSON.stringify(data.user));
      window.location.href = '/dashboard';
    } else {
      const errors = await response.json();
      displayErrors(errors.errors);
    }
  } catch (error) {
    console.error('Network error:', error);
  }
}

// 認証必須API呼び出し
async function fetchUserInfo() {
  const token = localStorage.getItem('token');
  
  try {
    const response = await fetch('/api/v1/auth/me', {
      headers: { 
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json'
      }
    });
    
    if (response.ok) {
      const data = await response.json();
      updateUserDisplay(data.user);
    } else if (response.status === 401) {
      // 認証エラー: ログアウト処理
      localStorage.removeItem('token');
      localStorage.removeItem('user');
      window.location.href = '/login';
    }
  } catch (error) {
    console.error('API error:', error);
  }
}

🎯 まとめ:認証フローの要点

✅ 成功パターン

  1. サインアップ/ログイン → JWTトークン発行
  2. トークン保存 → フロントエンドのローカルストレージ
  3. API呼び出し → Authorization: Bearer [token]
  4. 自動認証 → before_actionで透明な認証
  5. ユーザー特定 → current_userで即座にアクセス

🚨 エラーハンドリング

  1. バリデーションエラー → 422でフォーム再表示
  2. 認証エラー → 401でログイン画面へ
  3. トークン期限切れ → 自動ログアウト処理
  4. ネットワークエラー → リトライまたはエラー表示

この認証システムにより、安全で使いやすいAPIが実現できます🚀