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.
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.
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>
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 |
For simple requests (GET, HEAD, POST with certain content types), the browser sends the request directly:
Origin
header in requestFor complex requests, browsers send a preflight OPTIONS request first:
Origin
: The requesting originAccess-Control-Request-Method
: The intended methodAccess-Control-Request-Headers
: Custom headers (if any)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
<!-- 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>
<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>
# 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
// 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));
allowed_origins: "*"
with allow_credentials: true
These headers are always exposed to JavaScript without being listed in exposed_headers:
Cache-Control
Content-Language
Content-Type
Expires
Last-Modified
Pragma
Requests that don't trigger preflight must meet ALL these criteria:
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 |
curl -v
to see all headers./rusty-beam -v
CORS preflight requests are typically not authenticated. Ensure CORS plugin comes before authentication plugins to handle OPTIONS requests properly.
The security-headers plugin may add additional security headers. CORS headers take precedence for cross-origin requests.