OWASP API Security Top 10
| 順位 | 脆弱性 | 対策 |
|---|---|---|
| 1 | 認可の不備(BOLA) | オブジェクトレベルの権限チェック |
| 2 | 認証の不備 | 強力な認証メカニズム |
| 3 | オブジェクトプロパティレベルの認可不備 | レスポンスフィルタリング |
| 4 | リソース消費の制限なし | レート制限、ペイロードサイズ制限 |
| 5 | 機能レベルの認可不備 | RBAC/ABAC実装 |
| 6 | サーバーサイドリクエストフォージェリ | URL検証、ホワイトリスト |
| 7 | セキュリティ設定ミス | セキュリティヘッダー設定 |
| 8 | インジェクション | 入力検証、パラメータ化クエリ |
| 9 | 不適切な資産管理 | APIインベントリ管理 |
| 10 | 安全でないAPIの使用 | サードパーティAPI検証 |
認証・認可
JWT + リフレッシュトークン
import jwt from 'jsonwebtoken';
const ACCESS_TOKEN_EXPIRY = '15m';
const REFRESH_TOKEN_EXPIRY = '7d';
function generateTokens(userId: string, role: string) {
const accessToken = jwt.sign(
{ sub: userId, role, type: 'access' },
process.env.JWT_SECRET!,
{ expiresIn: ACCESS_TOKEN_EXPIRY }
);
const refreshToken = jwt.sign(
{ sub: userId, type: 'refresh' },
process.env.JWT_REFRESH_SECRET!,
{ expiresIn: REFRESH_TOKEN_EXPIRY }
);
return { accessToken, refreshToken };
}
// ミドルウェア
function authMiddleware(req: Request, res: Response, next: NextFunction) {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ error: 'No token provided' });
}
try {
const payload = jwt.verify(token, process.env.JWT_SECRET!, {
algorithms: ['HS256'], // アルゴリズム明示指定
});
req.user = payload;
next();
} catch (error) {
return res.status(401).json({ error: 'Invalid token' });
}
}
RBAC(ロールベースアクセス制御)
const permissions = {
admin: ['read', 'write', 'delete', 'manage_users'],
editor: ['read', 'write'],
viewer: ['read'],
};
function requirePermission(...requiredPermissions: string[]) {
return (req: Request, res: Response, next: NextFunction) => {
const userRole = req.user?.role;
const userPermissions = permissions[userRole] || [];
const hasPermission = requiredPermissions.every(
p => userPermissions.includes(p)
);
if (!hasPermission) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
}
// 使用
app.delete('/users/:id', authMiddleware, requirePermission('delete', 'manage_users'), deleteUser);
入力検証
import { z } from 'zod';
const CreateUserSchema = z.object({
email: z.string().email().max(255),
password: z.string().min(8).max(100)
.regex(/[A-Z]/, 'Must contain uppercase')
.regex(/[0-9]/, 'Must contain number'),
name: z.string().min(1).max(100)
.regex(/^[a-zA-Z\s]+$/, 'Only letters allowed'),
});
function validateBody<T>(schema: z.ZodSchema<T>) {
return (req: Request, res: Response, next: NextFunction) => {
const result = schema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({
error: 'Validation failed',
details: result.error.flatten().fieldErrors,
});
}
req.body = result.data;
next();
};
}
app.post('/users', validateBody(CreateUserSchema), createUser);
レート制限
import rateLimit from 'express-rate-limit';
import RedisStore from 'rate-limit-redis';
// 一般的なレート制限
const generalLimiter = rateLimit({
store: new RedisStore({ client: redisClient }),
windowMs: 15 * 60 * 1000, // 15分
max: 100,
message: { error: 'Too many requests' },
standardHeaders: true,
});
// 認証エンドポイント用(より厳しい)
const authLimiter = rateLimit({
store: new RedisStore({ client: redisClient }),
windowMs: 60 * 60 * 1000, // 1時間
max: 5, // 5回まで
message: { error: 'Too many login attempts' },
});
app.use('/api', generalLimiter);
app.use('/api/auth/login', authLimiter);
セキュリティヘッダー
import helmet from 'helmet';
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
},
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true,
},
}));
// CORS設定
app.use(cors({
origin: ['https://example.com', 'https://app.example.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
maxAge: 86400,
}));
SQLインジェクション対策
// 悪い例
const query = `SELECT * FROM users WHERE id = '${userId}'`;
// 良い例(パラメータ化クエリ)
const [users] = await db.query(
'SELECT * FROM users WHERE id = ?',
[userId]
);
// ORMを使用(さらに安全)
const user = await prisma.user.findUnique({
where: { id: userId },
});
機密データの保護
// レスポンスから機密フィールドを除外
function sanitizeUser(user: User): PublicUser {
const { password, refreshToken, ...publicData } = user;
return publicData;
}
// シリアライザパターン
class UserSerializer {
static toPublic(user: User): PublicUser {
return {
id: user.id,
email: user.email,
name: user.name,
createdAt: user.createdAt,
};
}
static toAdmin(user: User): AdminUser {
return {
...this.toPublic(user),
role: user.role,
lastLoginAt: user.lastLoginAt,
};
}
}
監査ログ
interface AuditLog {
timestamp: Date;
userId: string;
action: string;
resource: string;
resourceId: string;
ip: string;
userAgent: string;
success: boolean;
}
async function auditLog(log: AuditLog) {
await db.auditLogs.create({ data: log });
}
// ミドルウェア
function audit(action: string) {
return async (req: Request, res: Response, next: NextFunction) => {
const originalSend = res.send;
res.send = function(body) {
auditLog({
timestamp: new Date(),
userId: req.user?.id,
action,
resource: req.baseUrl,
resourceId: req.params.id,
ip: req.ip,
userAgent: req.headers['user-agent'],
success: res.statusCode < 400,
});
return originalSend.call(this, body);
};
next();
};
}
app.delete('/users/:id', audit('DELETE_USER'), deleteUser);
関連記事
- JWTの仕組み - トークン認証の詳細
- OAuth 2.0 / OIDC - 外部認証連携
- Webセキュリティベストプラクティス - 総合セキュリティ
- レート制限 - API保護の詳細
まとめ
APIセキュリティは、認証・認可、入力検証、レート制限、セキュリティヘッダーを組み合わせて実現します。OWASP API Top 10を参考に、包括的な対策を実装しましょう。
← Back to list