#はじめに
「CORSエラーが出て、どうすればいいかわからない」——Web開発で最もよく遭遇するエラーの一つです。
前回学んだ同一オリジンポリシーは、セキュリティのために別オリジンへのアクセスを制限します。しかし、現代のWebアプリケーションでは、別オリジンのAPIを呼び出すことは日常的です。CORSは、この制限を安全に緩和するための仕組みです。
この記事を読むと、以下のことができるようになります:
- CORSの仕組みを完全に理解できる
- プリフライトリクエストの役割がわかる
- CORSエラーの原因を特定し、対処できる
#CORSとは
CORS(Cross-Origin Resource Sharing)とは、サーバーが「このオリジンからのアクセスを許可する」と明示的に宣言することで、同一オリジンポリシーを緩和する仕組みです。
[ブラウザ: example.com] [サーバー: api.other.com]
| |
|------ GET /data ------------------>|
| Origin: https://example.com |
| |
|<----- Access-Control-Allow-Origin: |
| https://example.com |
| |
| ブラウザ: 「許可されている、読み取りOK」
サーバーがAccess-Control-Allow-Originヘッダーで許可を示すと、ブラウザはレスポンスをJavaScriptに渡します。
#シンプルリクエストとプリフライト
CORSには2種類のリクエストパターンがあります。
#シンプルリクエスト
以下の条件をすべて満たすリクエストは「シンプルリクエスト」として扱われます:
| 条件 | 許可される値 |
|---|---|
| メソッド | GET, HEAD, POST |
| ヘッダー | Accept, Accept-Language, Content-Language, Content-Type のみ |
| Content-Type | application/x-www-form-urlencoded, multipart/form-data, text/plain |
シンプルリクエストは、直接送信されます。
[ブラウザ] [サーバー]
| |
|------ GET /data ------------------>|
| Origin: https://example.com |
| |
|<----- 200 OK ----------------------|
| Access-Control-Allow-Origin: |
| https://example.com |
#プリフライトリクエスト
シンプルリクエストの条件を満たさないリクエストは、事前に「プリフライト」が送信されます。
[ブラウザ] [サーバー]
| |
|====== プリフライト(OPTIONS)======|
| |
|------ OPTIONS /data -------------->|
| Origin: https://example.com |
| Access-Control-Request-Method: PUT
| Access-Control-Request-Headers: Content-Type
| |
|<----- 204 No Content --------------|
| Access-Control-Allow-Origin: https://example.com
| Access-Control-Allow-Methods: PUT
| Access-Control-Allow-Headers: Content-Type
| |
|====== 実際のリクエスト =============|
| |
|------ PUT /data ------------------>|
| Origin: https://example.com |
| Content-Type: application/json
| |
|<----- 200 OK ----------------------|
#プリフライトが必要な例
// カスタムヘッダーを使用 → プリフライト発生
fetch('https://api.other.com/data', {
headers: {
'Authorization': 'Bearer token123', // カスタムヘッダー
'Content-Type': 'application/json' // application/json
}
});
// PUTメソッドを使用 → プリフライト発生
fetch('https://api.other.com/data', {
method: 'PUT',
body: JSON.stringify({ name: 'test' })
});
#CORSレスポンスヘッダー
サーバーがCORSを許可するために返すヘッダーです。
#Access-Control-Allow-Origin
許可するオリジンを指定します。
# 特定のオリジンを許可
Access-Control-Allow-Origin: https://example.com
# すべてのオリジンを許可(認証情報がない場合のみ)
Access-Control-Allow-Origin: *
注意: *と認証情報(Cookie等)の送信は併用できません。
#Access-Control-Allow-Methods
許可するHTTPメソッドを指定します(プリフライト用)。
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
#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.other.com/data', {
credentials: 'include' // Cookieを送信
});
#Access-Control-Expose-Headers
JavaScript からアクセスできるレスポンスヘッダーを指定します。
デフォルトでアクセスできるのは以下のみ:
- Cache-Control
- Content-Language
- Content-Type
- Expires
- Last-Modified
- Pragma
カスタムヘッダーを読み取るには、明示的に公開する必要があります:
Access-Control-Expose-Headers: X-Request-Id, X-Custom-Header
#Access-Control-Max-Age
プリフライトの結果をキャッシュする時間(秒)を指定します。
Access-Control-Max-Age: 86400 # 24時間
これにより、毎回プリフライトを送信する必要がなくなります。
#CORSリクエストヘッダー
ブラウザが自動的に付与するヘッダーです。
#Origin
リクエスト元のオリジンを示します。
Origin: https://example.com
#Access-Control-Request-Method
プリフライトで、実際のリクエストで使用するメソッドを示します。
Access-Control-Request-Method: PUT
#Access-Control-Request-Headers
プリフライトで、実際のリクエストで使用するヘッダーを示します。
Access-Control-Request-Headers: Content-Type, Authorization
#認証情報付きリクエスト
Cookieやセッション情報を含むリクエストには、追加の設定が必要です。
#クライアント側
fetch('https://api.other.com/data', {
credentials: 'include' // 必須
});
#サーバー側
Access-Control-Allow-Origin: https://example.com # * は使えない
Access-Control-Allow-Credentials: true
重要: 認証情報を送信する場合、Access-Control-Allow-Origin: *は使用できません。具体的なオリジンを指定する必要があります。
#CORSエラーの対処法
#エラー1: No ‘Access-Control-Allow-Origin’ header
Access to fetch at 'https://api.other.com/data' from origin
'https://example.com' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
原因: サーバーがCORSヘッダーを返していない
対処:
- サーバー側で
Access-Control-Allow-Originヘッダーを設定 - または、プロキシサーバー経由でアクセス
#エラー2: Origin is not allowed
Access to fetch at 'https://api.other.com/data' from origin
'https://example.com' has been blocked by CORS policy:
The 'Access-Control-Allow-Origin' header has a value 'https://allowed.com'
that is not equal to the supplied origin.
原因: サーバーが許可しているオリジンと、リクエスト元が異なる
対処: サーバーの許可リストにリクエスト元オリジンを追加
#エラー3: Credentials with wildcard
Access to fetch at 'https://api.other.com/data' from origin
'https://example.com' has been blocked by CORS policy:
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: *
対処: 具体的なオリジンを指定(*ではなくhttps://example.com)
#エラー4: Method not allowed
Access to fetch at 'https://api.other.com/data' from origin
'https://example.com' has been blocked by CORS policy:
Method PUT is not allowed by Access-Control-Allow-Methods in preflight response.
原因: プリフライトで返された許可メソッドに含まれていない
対処: Access-Control-Allow-Methodsに該当メソッドを追加
#エラー5: Header not allowed
Request header field authorization is not allowed by
Access-Control-Allow-Headers in preflight response.
原因: プリフライトで返された許可ヘッダーに含まれていない
対処: Access-Control-Allow-Headersに該当ヘッダーを追加
#サーバー実装例
#Express.js
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
}));
#nginx
location /api/ {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' 'https://example.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://example.com';
add_header 'Access-Control-Allow-Credentials' 'true';
}
#DevToolsでのデバッグ
#Networkタブで確認
- DevToolsのNetworkタブを開く
- プリフライト(OPTIONS)リクエストを探す
- リクエストとレスポンスのヘッダーを確認
確認ポイント:
- リクエストに
Originヘッダーがあるか - レスポンスに
Access-Control-Allow-Originがあるか - 許可されたメソッド/ヘッダーが正しいか
#Consoleでエラーを確認
CORSエラーは詳細なメッセージが表示されます。エラーメッセージをよく読むと、何が問題かがわかります。
#まとめ
- CORSは同一オリジンポリシーを安全に緩和する仕組み
- シンプルリクエスト: 直接送信、レスポンスヘッダーで判断
- プリフライトリクエスト: 事前にOPTIONSで許可を確認
- 重要なヘッダー:
Access-Control-Allow-Origin: 許可するオリジンAccess-Control-Allow-Methods: 許可するメソッドAccess-Control-Allow-Headers: 許可するヘッダーAccess-Control-Allow-Credentials: 認証情報の許可
- 認証情報付きリクエストでは
*は使えない
#次のステップ
CORSを理解したところで、次はCSP(Content Security Policy)について学びましょう。CSPは、XSS攻撃などからWebアプリケーションを保護するための強力なセキュリティ機構です。