CORS完全解説 - オリジン間リソース共有の仕組みとエラー対処法
CORS(Cross-Origin Resource Sharing)は、ブラウザが異なるオリジン間でのHTTPリクエストを制御する仕組み。サーバーが許可したオリジンのみがレスポンスを読み取れるようにする。
オリジンとは
オリジンは「スキーム + ホスト + ポート」の組み合わせ。1つでも異なれば**別オリジン(クロスオリジン)**となる。
https://example.com:443
スキーム: https
ホスト: example.com
ポート: 443
| URL | https://example.com との関係 |
|---|---|
https://example.com/api | 同一オリジン(パスは無関係) |
http://example.com | 別オリジン(スキームが違う) |
https://api.example.com | 別オリジン(サブドメインが違う) |
https://example.com:8080 | 別オリジン(ポートが違う) |
https://other.com | 別オリジン(ホストが違う) |
同一オリジンポリシー
ブラウザは**同一オリジンポリシー(Same-Origin Policy)**により、スクリプトが別オリジンのリソースを読み取ることを制限している。これはXSSやCSRFなどの攻撃からユーザーを守るためのセキュリティ機構。
CORSはこの制約を、サーバー側が明示的に「このオリジンからのアクセスを許可する」と宣言することで緩和する仕組み。
単純リクエスト(Simple Request)
以下の条件を全て満たすリクエストは単純リクエストとして扱われ、プリフライトなしに送信される。
メソッド: GET / HEAD / POST のいずれか
リクエストヘッダー: 以下のみ許可
AcceptAccept-LanguageContent-LanguageContent-Type(application/x-www-form-urlencoded/multipart/form-data/text/plainのいずれか)
sequenceDiagram
participant B as ブラウザ
participant S as サーバー
B->>S: GET /api/data
Note over B,S: Origin: https://example.com
S-->>B: 200 OK
Note over B,S: Access-Control-Allow-Origin: *
ブラウザは Origin ヘッダーを自動的に付与し、サーバーの Access-Control-Allow-Origin レスポンスヘッダーを確認する。オリジンが許可されていない場合、ブラウザはレスポンスをJavaScript から読めないようにブロックする(リクエスト自体は届いている)。
プリフライトリクエスト(Preflight Request)
単純リクエストの条件を満たさない場合(例: Authorization ヘッダーを含む、Content-Type: application/json、PUT/PATCH/DELETE メソッドなど)、ブラウザはまず OPTIONS リクエストを送り、サーバーが許可しているか確認する。
sequenceDiagram
participant B as ブラウザ
participant S as サーバー
rect rgb(230, 240, 255)
Note over B,S: プリフライトリクエスト
B->>S: OPTIONS /api/data
Note over B,S: Origin / Access-Control-Request-Method: POST<br/>Access-Control-Request-Headers: Content-Type, Authorization
S-->>B: 204 No Content
Note over B,S: Access-Control-Allow-Origin: https://example.com<br/>Access-Control-Allow-Methods: GET, POST, PUT<br/>Access-Control-Allow-Headers: Content-Type, Authorization<br/>Access-Control-Max-Age: 86400
end
rect rgb(230, 255, 230)
Note over B,S: 本リクエスト
B->>S: POST /api/data
Note over B,S: Origin / Content-Type: application/json<br/>Authorization: Bearer xxx
S-->>B: 200 OK
end
CORSレスポンスヘッダー
Access-Control-Allow-Origin
許可するオリジンを指定する。最も重要なヘッダー。
Access-Control-Allow-Origin: * # 全オリジンを許可
Access-Control-Allow-Origin: https://example.com # 特定オリジンのみ許可
*(ワイルドカード)は Authorization ヘッダーやCookieを含むリクエスト(credentials: 'include')では使用できない。複数オリジンを許可する場合は、リクエストの Origin ヘッダーを見てサーバー側で動的に返す。
Access-Control-Allow-Methods
許可するHTTPメソッドを指定する。プリフライトレスポンスで使用。
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers
許可するリクエストヘッダーを指定する。プリフライトレスポンスで使用。
Access-Control-Allow-Headers: Content-Type, Authorization, X-Custom-Header
Access-Control-Allow-Credentials
Cookieや Authorization ヘッダーを含むリクエストを許可する場合に必要。
Access-Control-Allow-Credentials: true
クライアント側でも credentials: 'include' の指定が必要。
fetch('https://api.example.com/data', {
credentials: 'include',
})
credentials: 'include' を使う場合は Access-Control-Allow-Origin に * は使えない。
Access-Control-Max-Age
プリフライトのキャッシュ時間(秒)。指定期間内は同じリクエストでプリフライトを省略できる。
Access-Control-Max-Age: 86400 # 24時間
Access-Control-Expose-Headers
デフォルトではブラウザが読める レスポンスヘッダーは限られている。追加のヘッダーを公開する場合に使用。
Access-Control-Expose-Headers: X-Total-Count, X-Request-Id
サーバー側の設定例
Node.js(Express)
const cors = require('cors')
// 全オリジン許可
app.use(cors())
// 特定オリジンのみ許可
app.use(cors({
origin: 'https://example.com',
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
}))
// 複数オリジンを動的に許可
const allowedOrigins = ['https://example.com', 'https://app.example.com']
app.use(cors({
origin: (origin, callback) => {
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true)
} else {
callback(new Error('Not allowed by CORS'))
}
},
credentials: true,
}))
Nginx
location /api/ {
add_header Access-Control-Allow-Origin "https://example.com" always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
add_header Access-Control-Allow-Headers "Content-Type, Authorization" always;
if ($request_method = OPTIONS) {
add_header Access-Control-Max-Age 86400;
return 204;
}
}
CloudFront(AWS)
CloudFrontでのCORS設定はオリジンリクエストポリシーで Origin ヘッダーを転送し、オリジンサーバーでCORSを設定するのが基本。
よくあるCORSエラーと対処法
エラー1: Access-Control-Allow-Origin がない
Access to fetch at 'https://api.example.com' from origin 'https://example.com'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is
present on the requested resource.
原因: サーバーがCORSヘッダーを返していない
対処: サーバー側でCORSヘッダーを追加する
エラー2: Credentials を使う場合の *
The value of the 'Access-Control-Allow-Origin' header in the response must not
be the wildcard '*' when the request's credentials mode is 'include'.
原因: credentials: 'include' のときに Access-Control-Allow-Origin: * を返している
対処: オリジンを明示する
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Credentials: true
エラー3: プリフライトが失敗する
Response to preflight request doesn't pass access control check:
It does not have HTTP ok status.
原因: OPTIONS リクエストに対してサーバーが 200/204 を返していない
対処: OPTIONS メソッドを明示的に処理する
app.options('*', cors()) // Expressの場合
エラー4: 許可されていないヘッダー
Request header field Authorization is not allowed by
Access-Control-Allow-Headers in preflight response.
原因: プリフライトレスポンスの Access-Control-Allow-Headers に必要なヘッダーが含まれていない
対処: 必要なヘッダーを追加する
Access-Control-Allow-Headers: Content-Type, Authorization
よくある誤解
誤解1: CORSはサーバーへのリクエストをブロックする
実際はブラウザがレスポンスをJavaScriptから読めないようにするだけで、リクエスト自体はサーバーに届く。DELETEリクエストなどは実行されている場合がある。
誤解2: CORSを無効にすれば解決する
ブラウザの拡張機能などでCORSを無効にする方法は開発中のデバッグ用途のみ。本番環境では必ずサーバー側で適切に設定する。
誤解3: * を設定すれば全て解決する
credentials: 'include' との組み合わせは使えない。また、不必要な全オリジン許可はセキュリティリスクになる。
参考リンク
- Fetch Standard - CORS protocol - W3C公式仕様
- Cross-Origin Resource Sharing (CORS) - MDN - MDNの解説(日本語)