CORS Plugin

The CORS (Cross-Origin Resource Sharing) plugin enables controlled access to resources from different origins. It handles preflight requests and adds appropriate CORS headers to responses, allowing web applications to make cross-origin requests securely.

Overview

Modern web browsers enforce the Same-Origin Policy, which restricts web pages from making requests to a different domain than the one serving the page. CORS provides a way to relax this restriction in a controlled manner. This plugin implements the CORS protocol by handling OPTIONS preflight requests and adding the necessary response headers.

Key Features

Configuration

The CORS plugin is configured as part of the plugin pipeline in your host configuration:

<li itemprop="plugin" itemscope itemtype="https://rustybeam.net/schema/Plugin">
    <span itemprop="library">file://./plugins/cors.so</span>
    <meta itemprop="allowed_origins" content="https://app.example.com,https://www.example.com">
    <meta itemprop="allowed_methods" content="GET,POST,PUT,DELETE">
    <meta itemprop="allow_credentials" content="true">
</li>

Configuration Parameters

Parameter Type Required Default Description
allowed_origins String (comma-separated) No "*" Allowed origins. Use "*" for all origins (not recommended for production)
allowed_methods String (comma-separated) No "GET,POST,PUT,DELETE,OPTIONS" Allowed HTTP methods
allowed_headers String (comma-separated) No "Content-Type,Authorization,X-Requested-With" Headers that clients can include in requests
exposed_headers String (comma-separated) No "" Headers exposed to the client beyond the safe list
allow_credentials Boolean No false Allow requests with credentials (cookies, auth headers)
max_age Integer No None How long (in seconds) browsers can cache preflight responses

CORS Request Flow

Simple Requests

For simple requests (GET, HEAD, POST with certain content types), the browser sends the request directly:

  1. Browser includes Origin header in request
  2. Server processes request normally
  3. CORS plugin adds appropriate headers to response
  4. Browser allows/blocks response based on headers

Preflight Requests

For complex requests, browsers send a preflight OPTIONS request first:

  1. Browser sends OPTIONS request with:
  2. CORS plugin responds with allowed methods, headers, etc.
  3. If allowed, browser sends the actual request
  4. Server processes and CORS plugin adds headers to response

Plugin Pipeline Placement

Important: The CORS plugin should be placed early in the pipeline to handle preflight requests before authentication or other processing.

Typical pipeline order:

1. cors.so            → Handles CORS preflight ✓
2. basic-auth.so      → Authenticates user
3. authorization.so   → Checks permissions
4. selector-handler.so → Processes requests
5. file-handler.so    → Serves files

Examples

Allow All Origins (Development)

<!-- WARNING: Only use in development! -->
<li itemprop="plugin" itemscope itemtype="https://rustybeam.net/schema/Plugin">
    <span itemprop="library">file://./plugins/cors.so</span>
    <meta itemprop="allowed_origins" content="*">
</li>

Production Configuration

<li itemprop="plugin" itemscope itemtype="https://rustybeam.net/schema/Plugin">
    <span itemprop="library">file://./plugins/cors.so</span>
    <meta itemprop="allowed_origins" content="https://app.example.com,https://mobile.example.com">
    <meta itemprop="allowed_methods" content="GET,POST,PUT,DELETE">
    <meta itemprop="allowed_headers" content="Content-Type,Authorization,X-API-Key">
    <meta itemprop="exposed_headers" content="X-Total-Count,X-Page-Number">
    <meta itemprop="allow_credentials" content="true">
    <meta itemprop="max_age" content="3600">
</li>

Testing CORS

# Test preflight request
curl -X OPTIONS http://localhost:3000/api/data \
  -H "Origin: https://app.example.com" \
  -H "Access-Control-Request-Method: POST" \
  -H "Access-Control-Request-Headers: Content-Type" \
  -v

# Test actual request
curl -X POST http://localhost:3000/api/data \
  -H "Origin: https://app.example.com" \
  -H "Content-Type: application/json" \
  -d '{"test": "data"}' \
  -v

JavaScript Fetch Example

// Simple CORS request
fetch('http://api.example.com/data', {
    method: 'GET',
    headers: {
        'Content-Type': 'application/json'
    }
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('CORS error:', error));

// Request with credentials
fetch('http://api.example.com/user', {
    method: 'GET',
    credentials: 'include', // Send cookies
    headers: {
        'Authorization': 'Bearer token123'
    }
})
.then(response => response.json())
.then(data => console.log(data));

Security Considerations

Security Warning: Improper CORS configuration can expose your API to security risks!

Common Security Issues

Best Practices

Browser Behavior

Safe Headers

These headers are always exposed to JavaScript without being listed in exposed_headers:

Simple Request Criteria

Requests that don't trigger preflight must meet ALL these criteria:

Troubleshooting

Common Issues

Issue Cause Solution
CORS error in browser console Origin not in allowed list Add origin to allowed_origins configuration
Preflight failing Method or header not allowed Add to allowed_methods or allowed_headers
Cookies not sent Credentials not enabled Set allow_credentials=true and use credentials:'include' in fetch
Headers not accessible in JS Headers not exposed Add headers to exposed_headers list
Wildcard origin with credentials Security restriction Specify exact origins when using credentials

Debug Tips

Integration with Other Plugins

Authentication Plugins

CORS preflight requests are typically not authenticated. Ensure CORS plugin comes before authentication plugins to handle OPTIONS requests properly.

Security Headers Plugin

The security-headers plugin may add additional security headers. CORS headers take precedence for cross-origin requests.

See Also