LESSON 30分

ストーリー

高橋アーキテクト
APIを作ったら、誰でもアクセスできるわけにはいかない

高橋アーキテクトが鍵のアイコンを描いた。

高橋アーキテクト
認証(Authentication)と認可(Authorization)の違い、分かるか?
あなた
認証は”誰であるか”の確認、認可は”何ができるか”の確認…ですか?
高橋アーキテクト
その通り。API設計では、この2つを明確に区別して設計する必要がある。混同すると、セキュリティホールの原因になる

認証と認可の違い

基本概念

// 認証(Authentication): あなたは誰?
// → ユーザーの身元を確認する
// 例: ログイン、トークン検証

// 認可(Authorization): あなたに何が許可されている?
// → ユーザーのアクセス権限を確認する
// 例: 管理者のみ削除可能、自分のデータのみ閲覧可能

// フロー
// 1. 認証: リクエストの送信者が誰かを確認
// 2. 認可: その送信者に要求された操作の権限があるかを確認
概念質問失敗時のステータス
認証あなたは誰?401 Unauthorizedログインしていない
認可何が許可されている?403 Forbidden管理者権限がない

認証方式の比較

1. APIキー

// 最もシンプルな認証方式
// ヘッダーまたはクエリパラメータで送信

// ヘッダー方式(推奨)
GET /api/v1/data
X-API-Key: sk_live_abc123def456

// クエリパラメータ方式(非推奨: ログに残る)
GET /api/v1/data?apiKey=sk_live_abc123def456

// サーバー側の検証
async function validateApiKey(apiKey: string): Promise<ApiClient | null> {
  const client = await db.apiClients.findByKey(apiKey);
  if (!client || client.isRevoked) return null;
  return client;
}

2. JWT(JSON Web Token)

// JWTの構造: header.payload.signature
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
// eyJzdWIiOiJ1c3JfMTIzIiwiZXhwIjoxNzA1MzkxNjAwfQ.
// SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

// ヘッダー(Base64)
{ "alg": "HS256", "typ": "JWT" }

// ペイロード(Base64)
{
  "sub": "usr_123",        // Subject(ユーザーID)
  "name": "田中太郎",
  "role": "admin",
  "iat": 1705305600,       // Issued At(発行日時)
  "exp": 1705391600        // Expiration(有効期限)
}

// 使用方法
GET /api/v1/users/me
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

// サーバー側の検証
import jwt from 'jsonwebtoken';

function verifyToken(token: string): JwtPayload {
  try {
    return jwt.verify(token, process.env.JWT_SECRET) as JwtPayload;
  } catch (error) {
    if (error instanceof jwt.TokenExpiredError) {
      throw new ApiError(401, 'TOKEN_EXPIRED', 'トークンが期限切れです');
    }
    throw new ApiError(401, 'INVALID_TOKEN', '無効なトークンです');
  }
}

3. OAuth 2.0

// OAuth 2.0 のフロー(Authorization Code Grant)
//
// 1. クライアント → 認可サーバー: 認可リクエスト
// GET /oauth/authorize?
//   response_type=code&
//   client_id=app_abc&
//   redirect_uri=https://app.example.com/callback&
//   scope=read:users write:orders&
//   state=xyz123
//
// 2. ユーザーが認可を許可
// 3. 認可サーバー → クライアント: 認可コード
// GET https://app.example.com/callback?code=AUTH_CODE&state=xyz123
//
// 4. クライアント → 認可サーバー: トークンリクエスト
// POST /oauth/token
// { grant_type: "authorization_code", code: "AUTH_CODE", ... }
//
// 5. 認可サーバー → クライアント: アクセストークン
// { access_token: "eyJ...", refresh_token: "ref_...", expires_in: 3600 }
//
// 6. クライアント → リソースサーバー: API呼び出し
// GET /api/v1/users/me
// Authorization: Bearer eyJ...

// スコープの設計
const SCOPES = {
  'read:users': 'ユーザー情報の読み取り',
  'write:users': 'ユーザー情報の書き込み',
  'read:orders': '注文情報の読み取り',
  'write:orders': '注文の作成・変更',
  'admin': '管理者権限',
} as const;

認証方式の使い分け

方式適する場面セキュリティ複雑さ
APIキーサーバー間通信、シンプルなAPI
JWTSPA、モバイルアプリ
OAuth 2.0サードパーティ連携最高
Basic認証開発・テスト環境のみ最低

認可の設計パターン

RBAC(ロールベースアクセス制御)

// ロールに基づいて権限を管理
interface Role {
  name: string;
  permissions: Permission[];
}

type Permission =
  | 'users:read'
  | 'users:write'
  | 'users:delete'
  | 'orders:read'
  | 'orders:write'
  | 'admin:all';

const ROLES: Record<string, Permission[]> = {
  viewer: ['users:read', 'orders:read'],
  editor: ['users:read', 'users:write', 'orders:read', 'orders:write'],
  admin: ['admin:all'],
};

// ミドルウェアでの認可チェック
function requirePermission(permission: Permission) {
  return (req: Request, res: Response, next: NextFunction) => {
    const user = req.user; // 認証済みユーザー
    const userPermissions = ROLES[user.role] || [];

    if (!userPermissions.includes(permission) &&
        !userPermissions.includes('admin:all')) {
      return res.status(403).json({
        error: {
          code: 'FORBIDDEN',
          message: 'この操作を行う権限がありません',
        }
      });
    }
    next();
  };
}

// 使用例
app.delete('/api/v1/users/:id', requirePermission('users:delete'), deleteUser);

リソースベースの認可

// ユーザーが自分のリソースのみ操作できる
async function getOrder(req: Request, res: Response) {
  const order = await orderRepository.findById(req.params.id);

  if (!order) {
    return res.status(404).json({ error: { code: 'NOT_FOUND' } });
  }

  // リソースの所有者チェック
  if (order.userId !== req.user.id && req.user.role !== 'admin') {
    return res.status(403).json({
      error: {
        code: 'FORBIDDEN',
        message: '他のユーザーの注文にはアクセスできません',
      }
    });
  }

  return res.json({ data: order });
}

セキュリティのベストプラクティス

// 1. トークンの有効期限を短くする
const accessToken = jwt.sign(payload, secret, { expiresIn: '15m' });
const refreshToken = jwt.sign(payload, secret, { expiresIn: '7d' });

// 2. HTTPS を必須にする
// 3. CORS を適切に設定する
// 4. レート制限を設ける(次のセクションで詳しく解説)
// 5. 機密情報をURLに含めない

まとめ

ポイント内容
認証 vs 認可認証は「誰か」、認可は「何ができるか」
APIキーシンプル、サーバー間通信向き
JWTステートレス、SPA/モバイル向き
OAuth 2.0サードパーティ連携、最高のセキュリティ
RBACロールに基づく権限管理

チェックリスト

  • 認証と認可の違いを説明できる
  • APIキー、JWT、OAuth 2.0の使い分けを理解した
  • RBACとリソースベース認可のパターンを把握した
  • セキュリティのベストプラクティスを理解した

次のステップへ

認証と認可の設計を学びました。

次のセクションでは、レート制限とスロットリングを学びます。 APIを安定的に運用するための重要な防御機構です。


推定読了時間: 30分