CORS

API | IT用語集

この用語をシェア

CORSとは

CORS(Cross-Origin Resource Sharing)は、異なるオリジン間でのリソース共有を安全に実現するためのWeb標準です。Same-Origin Policy(同一オリジンポリシー)の制約を緩和し、明示的に許可されたクロスオリジンリクエストを可能にします。Modern Web開発において、API連携やマイクロサービス間通信で必須の概念です。

オリジンとは

オリジンは、プロトコル + ドメイン + ポートの組み合わせで定義されます。

オリジンの例

URL プロトコル ドメイン ポート オリジン
https://example.com/api https example.com 443 https://example.com
http://example.com:8080/api http example.com 8080 http://example.com:8080
https://api.example.com/v1 https api.example.com 443 https://api.example.com

Same-Origin Policy(同一オリジンポリシー)

同一オリジンポリシーは、セキュリティ上の重要な制約です。異なるオリジンのリソースへのアクセスを制限することで、悪意のあるスクリプトからユーザーを保護します。

同一オリジンの判定

元のURL 比較対象URL 結果 理由
https://example.com/page https://example.com/api 同一オリジン すべて一致
https://example.com/page http://example.com/api クロスオリジン プロトコル違い
https://example.com/page https://api.example.com/v1 クロスオリジン ドメイン違い
https://example.com/page https://example.com:8080/api クロスオリジン ポート違い

CORSの動作メカニズム

Simple Request(シンプルリクエスト)

以下の条件を満たすリクエストは、プリフライトリクエストなしで送信されます。

Simple Requestの条件

  • メソッド:GET, HEAD, POST のいずれか
  • ヘッダー:Accept, Accept-Language, Content-Language, Content-Type(特定値のみ)
  • Content-Type:application/x-www-form-urlencoded, multipart/form-data, text/plain

Simple Requestの例

// クライアントからのリクエスト
GET /api/users HTTP/1.1
Host: api.example.com
Origin: https://frontend.example.com
Accept: application/json

// サーバーからのレスポンス
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://frontend.example.com
Content-Type: application/json

{
  "users": [{"id": 1, "name": "田中太郎"}]
}

Preflight Request(プリフライトリクエスト)

Simple Requestの条件を満たさないリクエストは、事前にOPTIONSメソッドでプリフライトリクエストを送信します。

プリフライトが必要な場合

  • メソッド:PUT, DELETE, PATCH等
  • カスタムヘッダー:Authorization, X-Custom-Header等
  • Content-Type:application/json等

プリフライトリクエストの例

// 1. プリフライトリクエスト
OPTIONS /api/users/123 HTTP/1.1
Host: api.example.com
Origin: https://frontend.example.com
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: Authorization

// 2. プリフライトレスポンス
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://frontend.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Max-Age: 86400

// 3. 実際のリクエスト
DELETE /api/users/123 HTTP/1.1
Host: api.example.com
Origin: https://frontend.example.com
Authorization: Bearer token123

// 4. 実際のレスポンス
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://frontend.example.com

CORSヘッダーの詳細

レスポンスヘッダー

ヘッダー 説明
Access-Control-Allow-Origin リクエストを許可するオリジン https://example.com
Access-Control-Allow-Methods 許可するHTTPメソッド GET, POST, PUT, DELETE
Access-Control-Allow-Headers 許可するリクエストヘッダー Authorization, Content-Type
Access-Control-Allow-Credentials 認証情報の送信を許可 true
Access-Control-Max-Age プリフライトの結果のキャッシュ時間 86400
Access-Control-Expose-Headers JavaScriptからアクセス可能なヘッダー X-Total-Count

リクエストヘッダー

ヘッダー 説明
Origin リクエストの送信元オリジン https://example.com
Access-Control-Request-Method 実際のリクエストで使用するメソッド DELETE
Access-Control-Request-Headers 実際のリクエストで使用するヘッダー Authorization

CORSの実装例

Node.js(Express)

const express = require('express');
const app = express();

// CORSミドルウェアの実装
app.use((req, res, next) => {
  const allowedOrigins = [
    'https://frontend.example.com',
    'https://app.example.com',
    'http://localhost:3000' // 開発環境
  ];
  
  const origin = req.headers.origin;
  
  if (allowedOrigins.includes(origin)) {
    res.header('Access-Control-Allow-Origin', origin);
  }
  
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
  res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization');
  res.header('Access-Control-Allow-Credentials', true);
  res.header('Access-Control-Max-Age', '86400');
  
  // プリフライトリクエストの処理
  if (req.method === 'OPTIONS') {
    res.sendStatus(200);
  } else {
    next();
  }
});

// corsパッケージを使用した実装
const cors = require('cors');

const corsOptions = {
  origin: function (origin, callback) {
    const allowedOrigins = [
      'https://frontend.example.com',
      'https://app.example.com'
    ];
    
    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('CORS policy violation'));
    }
  },
  credentials: true,
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization']
};

app.use(cors(corsOptions));

// API エンドポイント
app.get('/api/users', (req, res) => {
  res.json([
    { id: 1, name: '田中太郎' },
    { id: 2, name: '佐藤花子' }
  ]);
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

Python(Flask)

from flask import Flask, jsonify, request
from flask_cors import CORS, cross_origin

app = Flask(__name__)

# CORS設定
CORS(app, resources={
    r"/api/*": {
        "origins": [
            "https://frontend.example.com",
            "https://app.example.com"
        ],
        "methods": ["GET", "POST", "PUT", "DELETE"],
        "allow_headers": ["Content-Type", "Authorization"]
    }
})

# 個別のエンドポイントにCORSを適用
@app.route('/api/users', methods=['GET'])
@cross_origin(origins=['https://frontend.example.com'])
def get_users():
    return jsonify([
        {'id': 1, 'name': '田中太郎'},
        {'id': 2, 'name': '佐藤花子'}
    ])

# カスタムCORSミドルウェア
@app.after_request
def after_request(response):
    origin = request.headers.get('Origin')
    allowed_origins = [
        'https://frontend.example.com',
        'https://app.example.com'
    ]
    
    if origin in allowed_origins:
        response.headers.add('Access-Control-Allow-Origin', origin)
        response.headers.add('Access-Control-Allow-Headers', 'Content-Type, Authorization')
        response.headers.add('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
        response.headers.add('Access-Control-Allow-Credentials', 'true')
    
    return response

if __name__ == '__main__':
    app.run(debug=True)

Java(Spring Boot)

@RestController
@RequestMapping("/api")
public class UserController {

    // アノテーションを使用したCORS設定
    @CrossOrigin(
        origins = {"https://frontend.example.com", "https://app.example.com"},
        methods = {RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT, RequestMethod.DELETE},
        allowedHeaders = {"Content-Type", "Authorization"},
        allowCredentials = "true"
    )
    @GetMapping("/users")
    public List getUsers() {
        return Arrays.asList(
            new User(1, "田中太郎"),
            new User(2, "佐藤花子")
        );
    }
}

// グローバルCORS設定
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
                .allowedOrigins("https://frontend.example.com", "https://app.example.com")
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                .allowedHeaders("Content-Type", "Authorization")
                .allowCredentials(true)
                .maxAge(86400);
    }
}

フロントエンドでのCORS対応

JavaScript(Fetch API)

// 認証情報を含むリクエスト
fetch('https://api.example.com/users', {
  method: 'GET',
  credentials: 'include', // クッキーや認証情報を含める
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer ' + token
  }
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('CORS error:', error));

// POST リクエスト
fetch('https://api.example.com/users', {
  method: 'POST',
  credentials: 'include',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer ' + token
  },
  body: JSON.stringify({
    name: '新規ユーザー',
    email: 'newuser@example.com'
  })
})
.then(response => {
  if (!response.ok) {
    throw new Error('CORS request failed');
  }
  return response.json();
})
.then(data => console.log(data));

jQuery(AJAX)

$.ajaxSetup({
  beforeSend: function(xhr, settings) {
    if (settings.crossDomain) {
      xhr.setRequestHeader('Authorization', 'Bearer ' + token);
    }
  }
});

$.ajax({
  url: 'https://api.example.com/users',
  type: 'GET',
  dataType: 'json',
  xhrFields: {
    withCredentials: true // 認証情報を含める
  },
  success: function(data) {
    console.log(data);
  },
  error: function(xhr, status, error) {
    console.error('CORS error:', error);
  }
});

CORSのセキュリティ考慮事項

セキュリティベストプラクティス

  • オリジンの厳密な指定:ワイルドカード(*)は避ける
  • 認証情報の慎重な扱い:credentialsとワイルドカードの併用禁止
  • 必要最小限の権限:不要なメソッドやヘッダーは許可しない
  • プリフライトのキャッシュ:適切なMax-Age設定
  • Origin検証:サーバー側でのOriginヘッダー検証

よくあるセキュリティ問題

問題 リスク 対策
ワイルドカード(*)の使用 任意のオリジンからのアクセス 明示的なオリジン指定
認証情報の不適切な扱い 認証情報の漏洩 credentialsの適切な設定
過度な権限付与 不要なアクセス許可 必要最小限の権限設定
Origin検証の不備 偽装されたOriginの受け入れ サーバー側での厳密な検証

CORSエラーのトラブルシューティング

よくあるエラーメッセージ

  • "Access to fetch at 'URL' from origin 'ORIGIN' has been blocked by CORS policy"
  • "Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource"
  • "Response to preflight request doesn't pass access control check"

デバッグ方法

  1. ブラウザの開発者ツール:NetworkタブでCORSヘッダーを確認
  2. プリフライトリクエスト:OPTIONSメソッドのレスポンスを確認
  3. サーバーログ:CORSミドルウェアのログを確認
  4. curl テスト:コマンドラインでのテスト

curlを使用したテスト

# プリフライトリクエストのテスト
curl -X OPTIONS \
  -H "Origin: https://frontend.example.com" \
  -H "Access-Control-Request-Method: POST" \
  -H "Access-Control-Request-Headers: Authorization" \
  -v \
  https://api.example.com/users

# 実際のリクエストのテスト
curl -X GET \
  -H "Origin: https://frontend.example.com" \
  -H "Authorization: Bearer token123" \
  -v \
  https://api.example.com/users

CORSの代替手法

JSONP(JSON with Padding)

  • 用途:古いブラウザでのクロスオリジン通信
  • 制限:GETリクエストのみ、セキュリティリスクあり
  • 現状:現在は非推奨

プロキシサーバー

  • 用途:開発環境でのCORS回避
  • 実装:nginx、Apache、Webpack Dev Server
  • 注意:本番環境では適切なCORS設定が必要

Server-Sent Events(SSE)

  • 用途:サーバーからクライアントへの一方向通信
  • 特徴:CORSの制約が少ない
  • 制限:一方向通信のみ

CORSのベストプラクティス

  • 環境別設定:開発・ステージング・本番で異なる設定
  • ホワイトリスト方式:許可するオリジンの明示的な指定
  • 動的Origin検証:データベースやConfigからの動的な検証
  • 適切なエラーハンドリング:CORSエラーの適切な処理
  • モニタリング:CORSエラーの監視と分析

CORS対応のツール

  • Browser Extension:CORS Unblock、CORS Everywhere
  • Development Tools:Postman、Insomnia
  • Testing Tools:curl、HTTPie
  • Monitoring Tools:Sentry、LogRocket

関連技術

  • CSP:Content Security Policy
  • SOP:Same-Origin Policy
  • CSRF:Cross-Site Request Forgery
  • OAuth:認証・認可フレームワーク

関連Webサイト

この用語についてもっと詳しく

CORSに関するご質問や、システム導入のご相談など、お気軽にお問い合わせください。

カテゴリ

API IT用語集