CORS(Cross-Origin Resource Sharing)完全ガイド
CORSはクロスオリジンのHTTPリクエストを安全に許可するための仕組み。同一オリジンポリシーの制約を正しく理解し、適切に設定する方法を解説する。
この記事の対象読者
- CORSエラーに悩まされているフロントエンド/バックエンドエンジニア
- API開発でクロスオリジン通信を実装したい方
- 同一オリジンポリシーを正しく理解したい方
- Nginx / Django / Express でCORSを設定したい方
目次
Step 1同一オリジンポリシーとは
同一オリジンポリシー(Same-Origin Policy)は、ブラウザに組み込まれたセキュリティ機構です。あるオリジン(スキーム + ホスト + ポートの組み合わせ)から読み込まれたドキュメントやスクリプトが、別のオリジンのリソースにアクセスすることを制限します。
| URL | 同一オリジン? | 理由 |
|---|---|---|
https://example.com/page2 | はい | パスのみ異なる |
https://example.com:443/page2 | はい | 443はHTTPSのデフォルトポート |
http://example.com/page2 | いいえ | スキームが異なる(http vs https) |
https://api.example.com/page2 | いいえ | ホストが異なる(サブドメイン) |
https://example.com:8080/page2 | いいえ | ポートが異なる |
ポイント: 同一オリジンポリシーはブラウザの機能です。サーバー間の通信(curlやサーバーサイドのHTTPクライアント)には適用されません。CORSエラーが出るのはブラウザからのリクエストだけです。
Step 2CORSの仕組み
CORS(Cross-Origin Resource Sharing)は、サーバーが「どのオリジンからのリクエストを許可するか」をHTTPヘッダーで宣言する仕組みです。ブラウザはレスポンスのCORSヘッダーを確認し、許可されていれば結果をJavaScriptに渡し、許可されていなければブロックします。
1. ブラウザ: https://frontend.com から https://api.example.com にリクエスト
2. ブラウザ: Origin: https://frontend.com ヘッダーを自動付与
3. サーバー: Access-Control-Allow-Origin: https://frontend.com を返す
4. ブラウザ: Originが許可されているのでレスポンスをJSに渡すStep 3シンプルリクエストとプリフライト
ブラウザはリクエストの内容に応じて、直接送信(シンプルリクエスト)するか、事前確認(プリフライトリクエスト)を行うかを自動的に判断します。
シンプルリクエストの条件
- メソッドが GET、HEAD、POST のいずれか
- 手動設定したヘッダーが Accept、Accept-Language、Content-Language、Content-Type のみ
- Content-Type が application/x-www-form-urlencoded、multipart/form-data、text/plain のいずれか
プリフライトリクエスト
上記の条件を満たさない場合、ブラウザは本リクエストの前にOPTIONSメソッドでプリフライトリクエストを送信します。
OPTIONS /api/data HTTP/1.1
Host: api.example.com
Origin: https://frontend.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type, AuthorizationHTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://frontend.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400Access-Control-Max-Age: プリフライトの結果をブラウザがキャッシュする秒数です。86400(24時間)を設定すると、同じリクエストパターンのプリフライトが24時間キャッシュされ、パフォーマンスが向上します。
Step 4CORSヘッダー一覧
| ヘッダー | 方向 | 説明 |
|---|---|---|
Origin | リクエスト | リクエスト元のオリジン(ブラウザが自動付与) |
Access-Control-Allow-Origin | レスポンス | 許可するオリジン(* または特定オリジン) |
Access-Control-Allow-Methods | レスポンス | 許可するHTTPメソッド |
Access-Control-Allow-Headers | レスポンス | 許可するリクエストヘッダー |
Access-Control-Expose-Headers | レスポンス | JSからアクセスできるレスポンスヘッダー |
Access-Control-Allow-Credentials | レスポンス | Cookie等の認証情報を許可するか |
Access-Control-Max-Age | レスポンス | プリフライト結果のキャッシュ秒数 |
Step 5設定方法(Nginx / Django / Express)
Nginx での設定
server {
location /api/ {
# プリフライトリクエストの処理
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' 'https://frontend.com';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
add_header 'Access-Control-Max-Age' 86400;
return 204;
}
add_header 'Access-Control-Allow-Origin' 'https://frontend.com' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
proxy_pass http://backend;
}
}Django(django-cors-headers)
# pip install django-cors-headers
INSTALLED_APPS = [
...
"corsheaders",
]
MIDDLEWARE = [
"corsheaders.middleware.CorsMiddleware", # できるだけ先頭に配置
"django.middleware.common.CommonMiddleware",
...
]
# 許可するオリジンを明示的に指定
CORS_ALLOWED_ORIGINS = [
"https://frontend.com",
"https://admin.example.com",
]
# または正規表現で指定
CORS_ALLOWED_ORIGIN_REGEXES = [
r"^https://.*\.example\.com$",
]
# 認証情報付きリクエストを許可
CORS_ALLOW_CREDENTIALS = True
# 許可するヘッダー
CORS_ALLOW_HEADERS = [
"accept",
"authorization",
"content-type",
"origin",
"x-requested-with",
]Express(cors パッケージ)
const cors = require('cors');
const corsOptions = {
origin: ['https://frontend.com', 'https://admin.example.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
maxAge: 86400
};
app.use(cors(corsOptions));警告: Access-Control-Allow-Origin: * はすべてのオリジンを許可します。公開APIでない限り、必ず特定のオリジンを指定してください。また、* と credentials: true は併用できません。
Step 6認証情報付きリクエスト(credentials)
Cookie やAuthorizationヘッダーなどの認証情報をクロスオリジンリクエストに含めるには、フロントエンドとバックエンドの両方で設定が必要です。
fetch('https://api.example.com/data', {
method: 'GET',
credentials: 'include' // Cookieを送信
});Access-Control-Allow-Origin: https://frontend.com # * は不可
Access-Control-Allow-Credentials: true重要な制約: Access-Control-Allow-Credentials: true を使う場合、Access-Control-Allow-Origin に * は指定できません。必ず具体的なオリジンを指定する必要があります。
Step 7よくあるエラーと解決法
| エラーメッセージ | 原因 | 解決法 |
|---|---|---|
| No 'Access-Control-Allow-Origin' header is present | サーバーがCORSヘッダーを返していない | サーバー側でAccess-Control-Allow-Originを設定 |
| The value of the 'Access-Control-Allow-Origin' header must not be the wildcard '*' | credentials: includeと*の併用 | 具体的なオリジンを指定 |
| Method PUT is not allowed | Access-Control-Allow-Methodsに含まれていない | 許可メソッドにPUTを追加 |
| Request header field authorization is not allowed | Access-Control-Allow-Headersに含まれていない | 許可ヘッダーにAuthorizationを追加 |
| Redirect is not allowed for a preflight request | OPTIONSリクエストがリダイレクトされている | OPTIONSに対して直接204を返す |
デバッグのコツ: CORSエラーの調査は、ブラウザの開発者ツールの「ネットワーク」タブでリクエスト/レスポンスヘッダーを確認するのが最も効率的です。特にプリフライト(OPTIONS)リクエストのレスポンスヘッダーに注目してください。