Overview
Cross-Origin Resource Sharing (CORS) is a browser security mechanism that controls which external web applications can make HTTP requests to your Mango REST API. By default, browsers block web pages from making API calls to a different domain than the one serving the page. If you are building a custom web dashboard hosted on a separate server, a mobile web app, or any front-end application that needs to call the Mango API directly from the browser, you need to configure CORS.
This guide covers how CORS works, how to configure it in Mango's mango.properties, and how to verify it is working correctly.
Prerequisites
- Mango 4.x or 5.x
- Admin access to the Mango server file system
- Understanding of which external domains need API access
- Basic familiarity with the Mango REST API (see the Using the Mango REST API article)
What Is CORS and Why Does It Matter?
When a browser loads a web page from https://my-dashboard.example.com and that page makes a JavaScript fetch() call to https://mango.example.com:8443/rest/latest/data-points, the browser considers this a cross-origin request because the two URLs have different hostnames.
Before sending the actual request, the browser sends a preflight request (an HTTP OPTIONS request) to the Mango server asking whether it allows requests from that origin. If Mango does not respond with the appropriate CORS headers, the browser blocks the request and the JavaScript code receives an error.
CORS headers tell the browser:
- Which origins (domains) are allowed to make requests
- Which HTTP methods are permitted (GET, POST, PUT, DELETE)
- Which headers can be sent
- Whether credentials (cookies, tokens) are allowed
Step 1: Enable CORS in mango.properties
Mango's CORS settings are configured in the mango.properties file.
- Open the file:
# Linux
nano /opt/mango/overrides/properties/mango.properties
# Windows
notepad C:\\\\mango\\\\overrides\\\\properties\\\\mango.properties
- Add or update the following properties:
# Enable CORS support
rest.cors.enabled=true
# Allowed origins (comma-separated list of domains)
rest.cors.allowedOrigins=https://my-dashboard.example.com,https://another-app.example.com
# Allowed HTTP methods
rest.cors.allowedMethods=GET,POST,PUT,DELETE,OPTIONS
# Allowed request headers
rest.cors.allowedHeaders=Authorization,Content-Type,Accept,X-Requested-With
# Allow credentials (cookies, auth headers)
rest.cors.allowCredentials=true
# How long (in seconds) the browser caches the preflight response
rest.cors.maxAge=3600
- Save the file and restart Mango:
sudo systemctl restart mango
Step 2: Understand Each CORS Property
rest.cors.enabled
Set to true to enable CORS handling. When false (the default), Mango does not add any CORS headers to responses, and browsers will block all cross-origin requests.
rest.cors.allowedOrigins
A comma-separated list of origins that are permitted to make cross-origin requests. Each origin must include the scheme and hostname (and port, if non-standard).
# Single origin
rest.cors.allowedOrigins=https://dashboard.example.com
# Multiple origins
rest.cors.allowedOrigins=https://dashboard.example.com,https://admin.example.com,http://localhost:3000
# Allow all origins (NOT recommended for production)
rest.cors.allowedOrigins=*
DANGER:
Setting allowedOrigins=* permits any website to make API calls to your Mango server. This is acceptable for local development but is a serious security risk in production. Always list specific trusted origins in production environments.
rest.cors.allowedMethods
The HTTP methods that external applications are allowed to use. For full REST API access:
rest.cors.allowedMethods=GET,POST,PUT,DELETE,OPTIONS
If external applications only need read access, restrict to:
rest.cors.allowedMethods=GET,OPTIONS
rest.cors.allowedHeaders
The request headers that external applications can include. At minimum, you need:
Authorization \u2014 for bearer token or basic auth
Content-Type \u2014 for JSON request bodies
Accept \u2014 for content negotiation
rest.cors.allowCredentials
When set to true, the browser is allowed to send cookies and authentication headers with cross-origin requests. This is required if your external application uses session-based authentication or sends the Authorization header.
WARNING:
When allowCredentials is true, you cannot use the wildcard * for allowedOrigins. The browser enforces this restriction. You must list specific origins.
rest.cors.maxAge
The duration (in seconds) that the browser caches the preflight response. A value of 3600 (1 hour) is typical and reduces the number of preflight requests.
Step 3: Test CORS from the Browser
Using Browser Developer Tools
- Open your external web application in a browser
- Open Developer Tools (F12) > Network tab
- Trigger an API call to Mango from your JavaScript code
- Look for two requests:
- An
OPTIONS request (the preflight) \u2014 should return 200 OK with CORS headers
- The actual request (GET, POST, etc.) \u2014 should return the expected data
Check the Response Headers
On the preflight response, verify these headers are present:
Access-Control-Allow-Origin: https://my-dashboard.example.com
Access-Control-Allow-Methods: GET,POST,PUT,DELETE,OPTIONS
Access-Control-Allow-Headers: Authorization,Content-Type,Accept,X-Requested-With
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 3600
Using curl for Quick Testing
# Send a preflight request
curl -X OPTIONS "https://mango.example.com:8443/rest/latest/data-points" \\\\
-H "Origin: https://my-dashboard.example.com" \\\\
-H "Access-Control-Request-Method: GET" \\\\
-H "Access-Control-Request-Headers: Authorization" \\\\
-v 2>&1 | grep -i "access-control"
You should see the Access-Control-Allow-* headers in the response.
Step 4: CORS with Nginx Reverse Proxy
If you run Mango behind an Nginx reverse proxy, you have two options:
Option A: Let Mango Handle CORS
Configure CORS in mango.properties as described above. Ensure Nginx passes the Origin header through to Mango:
location /rest/ {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Origin $http_origin;
proxy_pass_header Access-Control-Allow-Origin;
proxy_pass_header Access-Control-Allow-Methods;
proxy_pass_header Access-Control-Allow-Headers;
proxy_pass_header Access-Control-Allow-Credentials;
}
Option B: Let Nginx Handle CORS
If you prefer to manage CORS at the proxy level, disable it in Mango (rest.cors.enabled=false) and add CORS headers in Nginx:
location /rest/ {
proxy_pass http://127.0.0.1:8080;
# CORS headers
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' 'https://my-dashboard.example.com';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Accept';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Max-Age' 3600;
return 204;
}
add_header 'Access-Control-Allow-Origin' 'https://my-dashboard.example.com' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
}
NOTE:
Do not enable CORS in both Mango and Nginx simultaneously. This can result in duplicate CORS headers, which browsers reject.
Step 5: Common Front-End Integration Patterns
JavaScript Fetch API
const response = await fetch('https://mango.example.com:8443/rest/latest/data-points', {
method: 'GET',
credentials: 'include', // Required when allowCredentials=true
headers: {
'Authorization': 'Bearer YOUR_TOKEN',
'Accept': 'application/json'
}
});
const data = await response.json();
Development with localhost
During development, add http://localhost:3000 (or your dev server port) to allowedOrigins:
rest.cors.allowedOrigins=https://production-app.example.com,http://localhost:3000
Remember to remove the localhost entry before deploying to production.
Security Best Practices
- Never use wildcard origins in production \u2014 Always list specific trusted domains in
rest.cors.allowedOrigins
- Restrict methods \u2014 If external apps only read data, limit to
GET,OPTIONS
- Use HTTPS \u2014 Always use HTTPS for both the Mango server and the external application in production
- Combine with authentication \u2014 CORS alone does not protect your API. Ensure all API requests require valid authentication tokens
- Review origins periodically \u2014 Remove origins for decommissioned applications
- Monitor access logs \u2014 Watch for unexpected origins in Mango's access logs
Verification
Confirm CORS is working correctly:
- ☐
rest.cors.enabled=true is set in mango.properties
- ☐ Mango has been restarted after configuration changes
- ☐ Browser preflight (
OPTIONS) requests return 200 with CORS headers
- ☐ Actual API requests succeed without CORS errors in the browser console
- ☐ The
Access-Control-Allow-Origin header matches your application's origin
- ☐ Credentials (cookies or auth headers) are sent and accepted if
allowCredentials=true
Troubleshooting
"Access to fetch has been blocked by CORS policy" in the browser console
This is the most common error. Check that rest.cors.enabled=true is set, that the origin of your web application is listed in rest.cors.allowedOrigins, and that Mango has been restarted after the change. Verify the origin matches exactly (scheme + hostname + port).
Preflight request returns 403 or 404
Mango may not be handling OPTIONS requests. Verify the CORS module is active and that rest.cors.allowedMethods includes OPTIONS.
"Cannot use wildcard in Access-Control-Allow-Origin when credentials flag is true"
If rest.cors.allowCredentials=true, you cannot set rest.cors.allowedOrigins=*. List your specific origins instead.
Duplicate CORS headers causing failures
If both Nginx and Mango are adding CORS headers, the browser receives duplicates and rejects the response. Choose one layer to handle CORS and disable it in the other.
CORS works for GET but fails for POST
Ensure POST is included in rest.cors.allowedMethods and that Content-Type is listed in rest.cors.allowedHeaders. POST requests with JSON bodies require a preflight, unlike simple GET requests.
Works in curl but not in the browser
curl does not enforce CORS \u2014 it is purely a browser security feature. If curl works but the browser blocks the request, the issue is always in the CORS response headers. Check the preflight response in the browser Network tab.