ストーリー
高橋アーキテクトが鍵のアイコンを描いた。
認証と認可の違い
基本概念
// 認証(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 | 中 | 低 |
| JWT | SPA、モバイルアプリ | 高 | 中 |
| 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分