Webセキュリティ

CORS(Cross-Origin Resource Sharing)完全ガイド — 仕組みと設定方法

セキュリティ CORS Web

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に渡し、許可されていなければブロックします。

CORSの基本フロー
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, Authorization
プリフライトレスポンス(サーバーが返す)
HTTP/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: 86400

Access-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 での設定

nginx.conf
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)

settings.py
# 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 パッケージ)

app.js
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 API)
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 allowedAccess-Control-Allow-Methodsに含まれていない許可メソッドにPUTを追加
Request header field authorization is not allowedAccess-Control-Allow-Headersに含まれていない許可ヘッダーにAuthorizationを追加
Redirect is not allowed for a preflight requestOPTIONSリクエストがリダイレクトされているOPTIONSに対して直接204を返す

デバッグのコツ: CORSエラーの調査は、ブラウザの開発者ツールの「ネットワーク」タブでリクエスト/レスポンスヘッダーを確認するのが最も効率的です。特にプリフライト(OPTIONS)リクエストのレスポンスヘッダーに注目してください。