本番環境のNode APIを堅牢にしましょう。正しいCORS、レート制限、セキュリティヘッダー、そして主要なOWASP APIリスクへの防御です。
このレッスンを Kodokon で開くOWASPはAPIに特化したトップ10を管理しています。支配的な三つのリスクは次のとおりです。BOLA(Broken Object Level Authorization、つまりidを変えて他のユーザーのオブジェクトにアクセスすること)、Broken Authentication(不十分に検証されたトークン、弱い秘密鍵)、そしてUnrestricted Resource Consumption(レート、サイズ、時間への制限がないこと)です。この三つのどれも、ライブラリをインストールすれば直るものではないことに注目してください。これらはあなたの設計そのものの性質です。まずは横断的な仕組みから始めましょう。CORS、レート制限、そしてヘッダーです。
import { createServer } from "node:http";
const allowed = new Set(["https://app.example.com"]);
const server = createServer((req, res) => {
const origin = req.headers.origin;
if (origin && allowed.has(origin)) {
res.setHeader(
"Access-Control-Allow-Origin",
origin,
);
res.setHeader("Vary", "Origin");
}
if (req.method === "OPTIONS") {
res.setHeader(
"Access-Control-Allow-Methods",
"GET,POST,PUT,DELETE",
);
res.setHeader(
"Access-Control-Allow-Headers",
"content-type,authorization",
);
res.setHeader("Access-Control-Max-Age", "86400");
res.writeHead(204).end();
return;
}
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify({ ok: true }));
});
server.listen(3000);CORSが何ではないかをはっきり理解しましょう。それはサーバーの保護ではありません。curlやサードパーティのバックエンドは、これらのヘッダーを完全に無視します。ブラウザだけがそれを強制し、自分のユーザーを守るのです。専門家の三つのルールがあります。許可リストから厳密なオリジンを返すこと(Originヘッダーを何も考えずにそのまま返してはいけません)、中間のキャッシュを汚染しないようVary: Originを加えること、そしてリクエストがクッキー(credentials)を運ぶやいなや*ワイルドカードがブラウザに拒否されることを知っておくことです。
const WINDOW_MS = 60_000;
const LIMIT = 100;
const hits = new Map();
function check(ip) {
const now = Date.now();
const entry = hits.get(ip);
if (!entry || now - entry.start >= WINDOW_MS) {
hits.set(ip, { start: now, count: 1 });
return { ok: true, remaining: LIMIT - 1 };
}
entry.count += 1;
const remaining = Math.max(LIMIT - entry.count, 0);
return { ok: entry.count <= LIMIT, remaining };
}
console.log(check("203.0.113.7"));セキュリティヘッダーは数行のコストで、攻撃のまるごと一群を防ぎます。HSTSは今後の訪問にHTTPSを強制し、X-Content-Type-Options: nosniffはブラウザがMIMEタイプを推測するのを防ぎ、制限的なCSP(default-src 'none')は、誤ってブラウザで表示されてしまったAPIレスポンスの悪用を無力化します。さらに、あなたの技術スタックを明かすヘッダーはすべて取り除きましょう。ExpressはデフォルトでX-Powered-Byを追加します。これはapp.disable('x-powered-by')で無効化できます。
function setSecurityHeaders(res) {
res.setHeader(
"Strict-Transport-Security",
"max-age=63072000; includeSubDomains",
);
res.setHeader("X-Content-Type-Options", "nosniff");
res.setHeader(
"Content-Security-Policy",
"default-src 'none'; frame-ancestors 'none'",
);
res.setHeader("Cache-Control", "no-store");
}
export { setSecurityHeaders };