Configuring CORS Settings
1. Setting Allowed Origins
| Value | Meaning | Security |
* | Any origin | Unsafe with credentials |
https://app.com | Single origin | Safe |
https://*.app.com | Subdomain wildcard | Safe (validate) |
| Regex | ^https://.*\.app\.com$ | Dynamic |
Example: CORS plugin (Kong)
plugins:
- name: cors
config:
origins: ["https://app.example.com", "https://admin.example.com"]
methods: [GET, POST, PUT, DELETE, OPTIONS]
headers: [Accept, Authorization, Content-Type, X-Request-ID]
exposed_headers: [X-Total-Count, X-Request-ID]
credentials: true
max_age: 3600
preflight_continue: false
2. Configuring Allowed Methods
| Method | Simple Request? |
| GET, HEAD, POST | Yes (no preflight) |
| PUT, PATCH, DELETE | No (preflight required) |
| OPTIONS | Used for preflight |
| Header | Notes |
Authorization | Required for bearer tokens |
Content-Type | Non-simple values need preflight |
X-Requested-With | AJAX detection |
X-CSRF-Token | CSRF protection |
* | Allow all Modern |
By default browsers expose only safelisted headers. Access-Control-Expose-Headers grants JS access to custom ones.
| Header | Use Case |
X-Total-Count | Pagination total |
X-RateLimit-* | Quota display |
Location | Created resource URL |
ETag | Cache validation |
5. Setting Max Age for Preflight Requests
| max_age | Effect |
| 0 | No cache, preflight every time |
| 600 | 10 min (Chrome cap) |
| 7200 | 2h (Firefox cap) |
| 86400 | 24h (Safari cap) |
6. Enabling Credentials Support
Warning: When credentials: true, origin MUST be specific (no *). Browsers reject otherwise.
| Setting | Effect |
Access-Control-Allow-Credentials: true | Sends cookies, Auth header |
Fetch credentials: "include" | Client opt-in |
XHR withCredentials = true | Legacy client opt-in |
7. Configuring Preflight Caching
Example: Preflight response
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Max-Age: 3600
Vary: Origin, Access-Control-Request-Method, Access-Control-Request-Headers
8. Using Dynamic CORS Configuration
Example: Per-tenant origin allowlist
local tenant = kong.request.get_header("x-tenant-id")
local origins = tenant_config[tenant].cors_origins
local req_origin = kong.request.get_header("origin")
if origins[req_origin] then
kong.response.set_header("Access-Control-Allow-Origin", req_origin)
kong.response.set_header("Vary", "Origin")
end
9. Implementing CORS Error Handling
| Error | Cause | Fix |
| No 'Access-Control-Allow-Origin' | Header missing | Add origin to allowlist |
| Credentials + wildcard | Spec violation | Use specific origin |
| Preflight fails (non-2xx) | OPTIONS blocked by auth | Allow OPTIONS without auth |
| Method not allowed | Verb missing from list | Extend allowed_methods |
10. Setting Origin Validation Rules
| Rule | Reason |
Reject null origin | File:// or sandboxed iframe |
| Validate scheme | Block http:// on prod |
Always include Vary: Origin | Cache correctness |
| Case-sensitive compare | Spec compliant |