1同一オリジンポリシーとは
ブラウザが課す同一オリジンポリシーを図で示す例です。別オリジンの API 呼び出しが既定でブロックされる、という制約が CORS 設定の出発点になります。
// オリジン = スキーム + ホスト + ポート の組
// https://app.example.com:443
// └─ scheme ─┘└─── host ────┘└port┘
//
// ブラウザの同一オリジンポリシー: 別オリジンへの呼び出しは既定で制限
//
// https://app.example.com のページから
// → https://app.example.com/users … 同一オリジン: OK
// → https://api.example.com/users … 別オリジン: 既定でブロック
//
// フロントと API のドメインが違う構成では、
// API 側が「このオリジンは許可する」と明示する必要がある = CORS
2enableCors の最小設定
main.ts で app.enableCors() を呼んで特定オリジンを許可する最小例です。これ1行で、指定したフロントエンドからのブラウザ経由のアクセスが通るようになります。
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// このオリジンからのブラウザ経由アクセスを許可する
app.enableCors({ origin: "https://app.example.com" });
await app.listen(3000);
}
bootstrap();
3CORSはブラウザにしか効かない
CORS が「ブラウザが課す制約」であって本当のアクセス制御ではないことを、プレーン TypeScript の判定で再現した例です。curl やサーバー間通信はオリジンを送らないため、CORS では止められないことが分かります。
TypeScript
4Helmet でセキュリティヘッダを付与する
helmet を適用してセキュリティ関連の HTTP ヘッダをまとめて付ける例です。クリックジャッキングや MIME スニッフィングを防ぐヘッダが自動で付くため、本番公開前の定番対策になります。
import { NestFactory } from "@nestjs/core";
import helmet from "helmet";
import { AppModule } from "./app.module";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// セキュリティ関連の HTTP ヘッダをまとめて付与する
// 例: X-Frame-Options(クリックジャッキング対策)
// X-Content-Type-Options(MIME スニッフィング対策)
app.use(helmet());
await app.listen(3000);
}
bootstrap();
5本番のオリジン許可リスト
環境変数で渡した複数の許可オリジンをリストで照合する、実務の本番向け CORS 設定です。origin にコールバックを渡すと、許可リストにあるオリジンだけを動的に通せます。
import { NestFactory } from "@nestjs/core";
import helmet from "helmet";
import { AppModule } from "./app.module";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 本番では許可オリジンを環境変数のリストで管理する
// 例: "https://app.example.com,https://admin.example.com"
const allowlist = (process.env.CORS_ORIGINS ?? "").split(",");
app.enableCors({
// origin を関数にすると許可可否を動的に判定できる
origin: (origin, callback) => {
// origin が無い(サーバー間)か、許可リストに含まれれば通す
if (!origin || allowlist.includes(origin)) {
callback(null, true);
} else {
callback(new Error("このオリジンは許可されていません"));
}
},
credentials: true, // Cookie 付きリクエストを許可
});
app.use(helmet());
await app.listen(3000);
}
bootstrap();