CORS Setup and Configuration
This tutorial will guide you through setting up Cross-Origin Resource Sharing (CORS) in your Gonyx application. You'll learn how to configure CORS middleware to control which domains can access your API, what HTTP methods are allowed, and how to handle preflight requests.
Prerequisites
Before starting this tutorial, make sure you have:
- Completed the Quick Start tutorial
- Basic understanding of HTTP and REST APIs
- Gonyx Framework v0.4.0 or later installed
- Familiarity with web security concepts
What is CORS?
Cross-Origin Resource Sharing (CORS) is a security mechanism that allows web browsers to make requests to servers on different domains than the one serving the web page. By default, browsers restrict cross-origin HTTP requests for security reasons.
Why You Need CORS
When building APIs, you typically need to enable CORS when:
- Your frontend application is hosted on a different domain than your API
- You want to allow specific external services to access your API
- You're developing locally with different ports for frontend and backend
- You need to control which HTTP methods and headers are allowed
Common Use Cases
- Frontend-Backend Separation: Your React/Vue/Angular app runs on
https://app.example.com
while your API runs onhttps://api.example.com
- Development Environment: Frontend on
localhost:3000
and API onlocalhost:8000
- Third-Party Integration: Allowing specific partner domains to access your API
Understanding CORS in Gonyx
Gonyx provides a built-in CORS middleware that can be easily configured through your HTTP server configuration. The middleware is built on top of the official gin-contrib/cors package, which is the standard CORS middleware for Gin framework.
How Gonyx Implements CORS
The Gonyx framework integrates the gin-contrib/cors middleware seamlessly into its configuration system, allowing you to configure CORS through JSON configuration files instead of writing Go code. This approach provides:
- Configuration-driven setup: Define CORS rules in your config files
- Environment-specific rules: Different CORS settings for dev/staging/prod
- Zero code changes: Update CORS policies without modifying your application code
- Built on industry standards: Uses the official Gin CORS middleware under the hood
The middleware handles:
- Preflight Requests: Automatic handling of OPTIONS requests
- Origin Validation: Checking if the requesting origin is allowed
- Header Management: Setting appropriate CORS headers in responses
- Credentials Support: Configuring cookie and authorization support
Step 1: Basic CORS Configuration
The CORS configuration is defined in your HTTP server configuration file. In a typical Gonyx project, this is located at configs/dev/http.json
.
Minimal Configuration
Here's a minimal CORS configuration that allows all origins (useful for development):
{
"default": "s1",
"servers": [
{
"name": "s1",
"addr": ":3000",
"versions": ["v1"],
"support_static": false,
"conf": {
"read_timeout": -1,
"write_timeout": -1,
"request_methods": ["ALL"]
},
"middlewares": {
"order": ["logger", "cors"],
"cors": {
"allow_all_origins": true,
"allow_origins": ["*"],
"allow_methods": ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"],
"allow_private_network": false,
"allow_headers": [],
"allow_credentials": false,
"expose_headers": [],
"max_age": 0,
"allow_wildcard": true,
"allow_browser_extension": false,
"custom_schemes": [],
"allow_websockets": false,
"allow_files": false,
"option_response_status_code": 204
}
}
}
]
}
The configuration above with allow_all_origins: true
is convenient for development but should not be used in production. Always specify explicit allowed origins for production environments.
Step 2: Production-Ready CORS Configuration
For production environments, you should explicitly specify which origins are allowed to access your API:
{
"default": "s1",
"servers": [
{
"name": "s1",
"addr": ":3000",
"versions": ["v1"],
"support_static": false,
"conf": {
"read_timeout": -1,
"write_timeout": -1,
"request_methods": ["ALL"]
},
"middlewares": {
"order": ["logger", "cors"],
"cors": {
"allow_all_origins": false,
"allow_origins": [
"https://app.example.com",
"https://www.example.com",
"https://admin.example.com"
],
"allow_methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
"allow_private_network": false,
"allow_headers": [
"Authorization",
"Content-Type",
"Accept",
"Origin",
"X-Requested-With"
],
"allow_credentials": true,
"expose_headers": ["X-Total-Count"],
"max_age": 86400,
"allow_wildcard": false,
"allow_browser_extension": false,
"custom_schemes": [],
"allow_websockets": false,
"allow_files": false,
"option_response_status_code": 204
}
}
}
]
}
This configuration:
- Explicitly lists allowed origins
- Restricts HTTP methods to only those needed
- Specifies which headers clients can send
- Enables credentials (cookies/authorization)
- Caches preflight responses for 24 hours (86400 seconds)
Step 3: Understanding CORS Configuration Options
Let's explore each CORS configuration option in detail:
Core Settings
allow_all_origins
(boolean)
When set to true
, allows requests from any origin. Use with caution!
"allow_all_origins": false // Recommended for production
allow_origins
(array of strings)
List of specific origins allowed to access your API. Each origin must include the protocol.
"allow_origins": [
"https://app.example.com",
"https://localhost:3000",
"http://192.168.1.100:8080"
]
You can use wildcards in domain names when allow_wildcard
is enabled:
"allow_origins": ["https://*.example.com"]
This allows any subdomain of example.com.
allow_methods
(array of strings)
HTTP methods that are allowed for cross-origin requests.
"allow_methods": ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]
Common configurations:
- Read-only API:
["GET", "HEAD", "OPTIONS"]
- Full CRUD API:
["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]
- Custom methods: Include any custom HTTP methods your API uses
Header Configuration
allow_headers
(array of strings)
Headers that the browser is allowed to send in the actual request.
"allow_headers": [
"Authorization",
"Content-Type",
"Accept",
"Origin",
"X-Requested-With",
"X-API-Key"
]
Common headers to allow:
Authorization
- For JWT or other auth tokensContent-Type
- Required for POST/PUT requests with JSONAccept
- Content negotiation- Custom headers like
X-API-Key
,X-Tenant-ID
, etc.
If allow_headers
is empty, the middleware will allow all headers from the request.
expose_headers
(array of strings)
Headers that the browser is allowed to access in the response.
"expose_headers": [
"X-Total-Count",
"X-Page-Number",
"X-Rate-Limit-Remaining"
]
By default, browsers only expose simple response headers. Use this to expose custom headers like pagination info or rate limits.
Credentials and Security
allow_credentials
(boolean)
Whether to allow requests with credentials (cookies, HTTP authentication, client-side SSL certificates).
"allow_credentials": true
When allow_credentials
is true
, you cannot use allow_all_origins: true
or wildcard origins. You must specify explicit origins.
allow_private_network
(boolean)
Whether to allow requests from private networks (as defined by the Private Network Access specification).
"allow_private_network": false
Enable this only if you need to allow requests from private IP addresses or localhost in production.
Advanced Options
max_age
(integer)
How long (in seconds) the browser should cache the preflight response.
"max_age": 86400 // 24 hours
0
- Browser makes a preflight request for every actual request86400
- Cache for 24 hours (recommended for production)-1
- Disable preflight caching
Setting a reasonable max_age
(e.g., 1-24 hours) reduces the number of preflight requests, improving performance.
allow_wildcard
(boolean)
Enable wildcard domain matching in allow_origins
.
"allow_wildcard": true
When enabled, you can use patterns like:
https://*.example.com
- Any subdomainhttps://app-*.example.com
- Specific pattern
option_response_status_code
(integer)
Status code to return for successful OPTIONS (preflight) requests.
"option_response_status_code": 204
Standard values:
204
- No Content (recommended)200
- OK (also acceptable)
Special Use Cases
allow_browser_extension
(boolean)
Allow requests from browser extensions.
"allow_browser_extension": false
Set to true
if you're building a browser extension that needs to access your API.
allow_websockets
(boolean)
Enable CORS for WebSocket connections.
"allow_websockets": false
Required if your API uses WebSocket connections across domains.
allow_files
(boolean)
Allow file:// protocol origins (local files).
"allow_files": false
Enabling this can be a security risk. Only enable for specific development or testing scenarios.
custom_schemes
(array of strings)
Allow custom URL schemes beyond http/https.
"custom_schemes": ["app://", "mycustomapp://"]
Useful for native mobile apps or desktop applications with custom URL schemes.
Step 4: Middleware Ordering
The order of middlewares matters! CORS should typically come early in the middleware chain but after the logger:
"middlewares": {
"order": ["logger", "cors", "auth", "rate-limit"],
"logger": { ... },
"cors": { ... },
"auth": { ... },
"rate-limit": { ... }
}
Why Order Matters
- Logger first: Logs all requests including CORS preflight requests
- CORS early: Handles preflight requests before they reach authentication
- Auth after CORS: OPTIONS requests don't need authentication
- Rate limiting last: Applied after CORS validation
Step 5: Testing Your CORS Configuration
After configuring CORS, you should test it to ensure it works correctly.
Testing with cURL
Test a simple GET request:
curl -H "Origin: https://app.example.com" \
-H "Access-Control-Request-Method: GET" \
-H "Access-Control-Request-Headers: Content-Type" \
-X OPTIONS \
http://localhost:3000/api/v1/Sample/hello \
-v
Look for these response headers:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type
Access-Control-Max-Age: 86400
Testing with a Frontend Application
Create a simple HTML file to test CORS:
<!DOCTYPE html>
<html>
<head>
<title>CORS Test</title>
</head>
<body>
<h1>CORS Test</h1>
<button id="testGet">Test GET Request</button>
<button id="testPost">Test POST Request</button>
<div id="result"></div>
<script>
const apiUrl = 'http://localhost:3000/api/v1/Sample';
document.getElementById('testGet').addEventListener('click', async () => {
try {
const response = await fetch(`${apiUrl}/hello`);
const data = await response.text();
document.getElementById('result').innerHTML =
``;
} catch (error) {
document.getElementById('result').innerHTML =
``;
}
});
document.getElementById('testPost').addEventListener('click', async () => {
try {
const response = await fetch(`${apiUrl}/test-cors`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ test: 'data', timestamp: Date.now() })
});
const data = await response.json();
document.getElementById('result').innerHTML =
``;
} catch (error) {
document.getElementById('result').innerHTML =
``;
}
});
</script>
</body>
</html>
Open this file in your browser and click the buttons to test CORS.
Common CORS Errors
Error: "No 'Access-Control-Allow-Origin' header is present"
Solution: Ensure the requesting origin is in your allow_origins
list.
Error: "Credential is not supported if the CORS header 'Access-Control-Allow-Origin' is '*'"
Solution: Set allow_all_origins: false
and specify explicit origins when using allow_credentials: true
.
Error: "Method [METHOD] is not allowed by Access-Control-Allow-Methods"
Solution: Add the required HTTP method to your allow_methods
array.
Error: "Request header field [HEADER] is not allowed by Access-Control-Allow-Headers"
Solution: Add the required header to your allow_headers
array.
Step 6: Environment-Specific Configuration
You should maintain different CORS configurations for different environments:
Development Configuration
{
"middlewares": {
"cors": {
"allow_all_origins": true,
"allow_origins": ["*"],
"allow_methods": ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"],
"allow_credentials": false,
"max_age": 0
}
}
}
Staging Configuration
{
"middlewares": {
"cors": {
"allow_all_origins": false,
"allow_origins": [
"https://staging-app.example.com",
"https://staging-admin.example.com"
],
"allow_methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
"allow_credentials": true,
"max_age": 3600
}
}
}
Production Configuration
{
"middlewares": {
"cors": {
"allow_all_origins": false,
"allow_origins": [
"https://app.example.com",
"https://www.example.com",
"https://admin.example.com"
],
"allow_methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
"allow_credentials": true,
"allow_headers": [
"Authorization",
"Content-Type",
"Accept"
],
"expose_headers": ["X-Total-Count"],
"max_age": 86400,
"allow_wildcard": false
}
}
}
Best Practices
Security Best Practices
-
Never use
allow_all_origins: true
in production- Always specify explicit origins
- Use environment-specific configurations
-
Be restrictive with allowed methods
- Only allow methods your API actually uses
- Remove OPTIONS from the list if you're handling it automatically
-
Carefully consider credentials
- Only enable
allow_credentials
if you need cookies or auth headers - Never combine credentials with wildcard origins
- Only enable
-
Validate origins strictly
- Don't use overly broad wildcard patterns
- Regularly review and update allowed origins
-
Use HTTPS in production
- Always use
https://
origins in production - HTTP origins can be security vulnerabilities
- Always use
Performance Best Practices
-
Set appropriate max_age
- Use 1-24 hours in production to reduce preflight requests
- Consider user experience vs. security tradeoffs
-
Minimize allowed headers
- Only allow headers your API actually needs
- Reduces preflight complexity
-
Enable wildcard matching carefully
- Use for legitimate subdomain patterns only
- Don't over-rely on wildcards
Development Best Practices
-
Use different configs per environment
- Maintain separate CORS configs for dev/staging/prod
- Use environment variables when needed
-
Document your CORS requirements
- Keep a list of all domains that need access
- Document why each origin is allowed
-
Test thoroughly
- Test preflight requests
- Test actual requests with various methods
- Test with and without credentials
-
Monitor CORS errors
- Log CORS-related errors
- Set up alerts for unusual CORS failures
Common Scenarios
Scenario 1: Single-Page Application (SPA)
Frontend on https://app.example.com
, API on https://api.example.com
:
"cors": {
"allow_all_origins": false,
"allow_origins": ["https://app.example.com"],
"allow_methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
"allow_credentials": true,
"allow_headers": ["Authorization", "Content-Type"],
"max_age": 86400
}
Scenario 2: Mobile App with Custom Scheme
Mobile app using myapp://
scheme:
"cors": {
"allow_all_origins": false,
"allow_origins": ["myapp://"],
"custom_schemes": ["myapp://"],
"allow_methods": ["GET", "POST", "PUT", "DELETE"],
"allow_credentials": false
}
Scenario 3: Multiple Subdomains
All subdomains of example.com need access:
"cors": {
"allow_all_origins": false,
"allow_origins": ["https://*.example.com"],
"allow_wildcard": true,
"allow_methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
"allow_credentials": true,
"max_age": 86400
}
Scenario 4: Public API
Public API with no authentication:
"cors": {
"allow_all_origins": true,
"allow_origins": ["*"],
"allow_methods": ["GET", "OPTIONS"],
"allow_credentials": false,
"max_age": 86400
}
Scenario 5: API with Rate Limiting Headers
API that exposes rate limit information:
"cors": {
"allow_all_origins": false,
"allow_origins": ["https://app.example.com"],
"allow_methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
"allow_credentials": true,
"expose_headers": [
"X-RateLimit-Limit",
"X-RateLimit-Remaining",
"X-RateLimit-Reset"
],
"max_age": 3600
}
Troubleshooting
CORS Still Not Working?
-
Check middleware order
- Ensure CORS middleware is registered
- Verify it's in the correct position
-
Verify configuration syntax
- Check for JSON syntax errors
- Ensure all required fields are present
-
Test preflight separately
- Use browser dev tools to inspect OPTIONS requests
- Check response headers
-
Review server logs
- Look for CORS-related errors
- Check if requests are reaching your handlers
-
Clear browser cache
- Browsers cache preflight responses
- Clear cache or use incognito mode for testing
Debugging Tips
Enable verbose logging to see CORS decisions:
"middlewares": {
"order": ["logger", "cors"],
"logger": {
"format": "> [${time}] ${status} - ${latency} ${method} ${path} ${queryParams}\n",
"output": "stdout"
},
"cors": { ... }
}
Use browser developer tools:
- Open DevTools (F12)
- Go to Network tab
- Look for OPTIONS requests
- Check request and response headers
Conclusion
In this tutorial, you learned how to:
- Understand what CORS is and why it's important
- Configure CORS middleware in Gonyx Framework
- Use different CORS configurations for development and production
- Test CORS functionality
- Troubleshoot common CORS issues
- Apply best practices for security and performance
CORS is a critical security feature for modern web APIs. By properly configuring CORS in your Gonyx application, you ensure that your API is both secure and accessible to legitimate clients.
Next Steps
- Add authentication middleware after CORS
- Implement rate limiting to protect your API
- Set up API monitoring to track CORS failures
- Configure additional security headers
- Explore API versioning strategies
Additional Resources
Official Documentation
- Gonyx Framework Documentation - Main Gonyx documentation
- gin-contrib/cors GitHub - Official CORS middleware used by Gonyx
- Gin Web Framework - The underlying web framework
CORS Standards & Specifications
- MDN CORS Documentation - Comprehensive guide to CORS
- W3C CORS Specification - Official CORS specification
- Private Network Access - Specification for private network requests
Security Resources
- OWASP CORS Cheat Sheet - Security best practices