2026/03/31
CORS
同一オリジンポリシー(Same-Origin Policy)
ブラウザが持つセキュリティの仕組みで、「あるオリジンから読み込まれた Web ページが、別のオリジンのリソースに JavaScript からアクセスすることを制限する」ポリシー
オリジンとは以下の3要素の組み合わせ
1つでも異なれば「別オリジン(クロスオリジン)」になる
origin = scheme + host + port
https://app.example.com:443/path
↑ ↑ ↑
scheme host port
【Same Origin】
https://example.com
- https://example.com/page-a # 同一(path のみ異なる)
- https://example.com/page-b # 同一(path のみ異なる)
【Cross Origin】
https://example.com
- http://example.com # scheme が異なる
- https://api.example.com # sub domain が異なる
- https://example.com:8080 # port が異なる
なぜ同一オリジンポリシーが必要か
下記のような事象を防ぐため
① ユーザーが A にアクセスする
② A の JavaScript が裏で全く異なるサービス B (クロスオリジン)にリクエスト
③ ユーザーのブラウザには B のセッション Cookie がある
④ B は正規ユーザーのリクエストとして処理してしまう
→ 勝手に処理(送金など)される
CORS(Cross-Origin Resource Sharing)とは
同一オリジンポリシーの制限をサーバー側が明示的に許可することで安全にクロスオリジンアクセスを可能にする仕組み
「コルス」と呼ばれる
app.example.com(フロントエンド)
↓ fetch('https://api.example.com/users')
api.example.com(バックエンド)
↓ レスポンスヘッダーに CORS 許可を付与
Access-Control-Allow-Origin: https://app.example.com
↓
ブラウザが許可を確認し、レスポンスをフロントに渡す
原因・解決方法
エラー発生ケース
Access-Control-Allow-Originヘッダー不足/不一致
サーバーが CORS を許可していない
プリフライトリクエスト(Preflight Request、OPTIONS)失敗
サーバーがOPTIONSメソッドを処理していない
credentials 向けの設定不足
Cookie・Authorization を含む場合、特別な設定が必要
許可外のヘッダー・メソッド使用
カスタムヘッダーやPUT/DELETEが許可されていない
解決方法(基本)
レスポンスヘッダー設定を追加・見直し
Access-Control-Allow-Origin: https://app.example.com
→ アクセスを許可するオリジンを指定する
→ * を指定すると全オリジンを許可(credentials 使用時は * 不可)
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
→ 許可する HTTP メソッドを指定する
Access-Control-Allow-Headers: Content-Type, Authorization, X-Custom-Header
→ 許可するリクエストヘッダーを指定する
Access-Control-Allow-Credentials: true
→ Cookie・認証情報を含むリクエストを許可する
Access-Control-Max-Age: 86400
→ プリフライトリクエストの結果をキャッシュする秒数
→ 設定することで OPTIONS リクエストの回数を減らせる
Access-Control-Expose-Headers: X-Custom-Header
→ JavaScript から読み取れるレスポンスヘッダーを追加する
- https://developer.mozilla.org/ja/docs/Web/HTTP/Reference/Headers/Access-Control-Allow-Methods
- https://developer.mozilla.org/ja/docs/Web/HTTP/Reference/Headers/Access-Control-Allow-Headers
- https://developer.mozilla.org/ja/docs/Web/HTTP/Reference/Headers/Access-Control-Allow-Credentials
- https://developer.mozilla.org/ja/docs/Web/HTTP/Reference/Headers/Access-Control-Max-Age
- https://developer.mozilla.org/ja/docs/Web/HTTP/Reference/Headers/Access-Control-Expose-Headers
プリフライトリクエスト(Preflight Request、OPTIONS)
プリフライトリクエストとは
クロスオリジンリクエストを送る前に、ブラウザが自動的に事前確認(OPTIONSリクエスト) をサーバーに送る仕組み
サーバーが許可していることを確認してから本リクエストを送る
単純リクエスト vs プリフライトリクエスト
単純リクエスト(プリフライト不要)
以下の条件をすべて満たす場合:
メソッド:GET / HEAD / POST のみ
ヘッダー:以下のみ使用
Accept / Accept-Language / Content-Language
Content-Type(application/x-www-form-urlencoded /
multipart/form-data / text/plain のみ)
プリフライトリクエスト
・PUT / DELETE / PATCH などのメソッドを使う
・Content-Type: application/json を使う
・Authorization などのカスタムヘッダーを付ける
・X-Custom-Header などの独自ヘッダーを付ける
プリフライトリクエストの流れ
① ブラウザが OPTIONS リクエストを自動送信
OPTIONS /api/users HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, Authorization
② サーバーが OPTIONS に対してレスポンスを返す
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400
③ ブラウザが許可を確認して本リクエストを送信
POST /api/users HTTP/1.1
Origin: https://app.example.com
Content-Type: application/json
Authorization: Bearer eyJ...
④ サーバーが本リクエストを処理してレスポンス
HTTP/1.1 201 Created
Access-Control-Allow-Origin: https://app.example.com
Content-Type: application/json
credentials(Cookie・認証情報)
credentials とは
Cookie・HTTP 認証・クライアント証明書などの認証情報をクロスオリジンリクエストに含めるかどうかを制御する設定
クライアント側の設定
// Cookie を一切送らない
fetch(url, { credentials: 'omit' })
// 同一オリジンのみ Cookie を送る
fetch(url, { credentials: 'same-origin' })
// クロスオリジンでも Cookie を送る
fetch(url, { credentials: 'include' })
サーバー側の設定
credentials: 'include' を使う場合:
- Access-Control-Allow-Credentials: true を付ける
- Access-Control-Allow-Origin に * は使えない(明示的なオリジンが必要)
Access-Control-Allow-Origin: https://app.example.com ← 明示的に指定
Access-Control-Allow-Credentials: true
Cookie SameSite 属性
クロスオリジンリクエストで Cookie を送るには Cookie のSameSite属性も考慮が必要
SameSite=Strict → クロスオリジンでは Cookie を送らない
SameSite=Lax → GET 系のナビゲーションのみ送る
SameSite=None → クロスオリジンでも送る(Secure 属性必須)
クロスオリジン API に Cookie を送りたい場合:
Set-Cookie: session_id=abc123; SameSite=None; Secure; HttpOnly
fetch 設定
mode
3つのモードが存在する
fetch(url, { mode: 'cors' })
// → クロスオリジンリクエストを CORS で許可する(デフォルト)
// → サーバーが CORS ヘッダーを返さないとエラーになる
fetch(url, { mode: 'no-cors' })
// → CORS ヘッダーなしでリクエストを送る
// → レスポンスの中身は一切読めない(opaque response)
fetch(url, { mode: 'same-origin' })
// → クロスオリジンリクエストは必ずエラーにする
// → 同一オリジンのみ許可したい場合に使う
注意点
Access-Control-Allow-Origin: * と credentials の併用不可
# NG:明示的なオリジンでない
Access-Control-Allow-Origin: *
# OK:明示的なオリジン
Access-Control-Allow-Origin: https://app.example.com
環境ごとのオリジン管理
環境ごとのオリジンを環境変数で管理する
# NG:本番コードに localhost を直書きする
# → 本番環境にローカルホストへのアクセス許可が残ってしまう
origins 'https://app.example.com', 'http://localhost:3000'
# OK:環境変数で管理する
# config/initializers/cors.rb
allowed_origins = ENV.fetch('CORS_ALLOWED_ORIGINS', 'http://localhost:3000')
.split(',')
.map(&:strip)
# .env.production
CORS_ALLOWED_ORIGINS=https://app.example.com
# .env.local
CORS_ALLOWED_ORIGINS=http://localhost:3000
OPTIONSリクエストに認証ミドルウェアをかけない
# NG:OPTIONS リクエストにも JWT 認証をかけてしまう
class ApplicationController < ActionController::API
before_action :authenticate_user! # OPTIONS にも実行されてしまう
end
# OK:OPTIONS は認証をスキップする
class ApplicationController < ActionController::API
before_action :authenticate_user!
private
def authenticate_user!
# プリフライトリクエストは認証をスキップする
return if request.method == 'OPTIONS'
# 以降の認証処理
end
end
ロードバランサー・リバースプロキシでの CORS 設定の二重付与
アプリサーバー と Nginx(またはロードバランサー)の両方で CORS ヘッダーを付けると重複して付与されエラーになる
CORS ヘッダーはどちらか一方にだけ設定する
アプリサーバー(Rails, Laravel, Express など)のみで設定
→ Nginx では CORS を設定しない
or
Nginx のみで設定
→ アプリサーバーでは CORS を設定しない
開発環境でのプロキシを使った回避
開発中で都度サーバーの設定を変えるのが面倒な場合、フロントエンドの開発サーバーにプロキシを設定することで CORS を回避可能
// /api/users などへのリクエストを localhost:8080/api に転送する
// 同一オリジンとして扱われるため CORS が発生しない