Security
CORS Configuration
Allowed origin rules, browser behavior, and safe expectations for cross-origin SSO consumers.
5.31.1
Overview
Cross-Origin Resource Sharing (CORS) allows your application to make secure requests to the SSO service from a different origin (domain). This is essential for OAuth 2.0 flows and API interactions.
Important
Only origins configured on the SSO deployment itself (via the SSO_ALLOWED_ORIGINS environment variable) receive a matching Access-Control-Allow-Origin header. If your origin isn't in that list, the browser will block your JavaScript from reading SSO API responses even though the request itself reaches the server.
SSO CORS Policy
The SSO service implements the following CORS policy:
- ✅ Allowed Origins: Only origins listed in the server's
SSO_ALLOWED_ORIGINSconfiguration (wildcard support exists in the underlying config but is not enabled by default) - ✅ Credentials: Cookies are allowed (
Access-Control-Allow-Credentials: true) - ✅ Methods: GET, POST, PUT, DELETE, OPTIONS
- ✅ Headers: Content-Type, Authorization
Registering Your Origin
CORS origins are not self-service — they're set directly in the SSO deployment's SSO_ALLOWED_ORIGINS environment variable (a comma-separated list) by whoever operates that deployment. To get your origin added:
- Determine your application's origin(s) (e.g.,
https://myapp.com) — must be HTTPS in production - Ask the operator of your SSO deployment to add it to
SSO_ALLOWED_ORIGINSand restart/redeploy the service
Local development note
CORS has no automatic localhost allowance. The default SSO_ALLOWED_ORIGINS only includes the service's production domains, so a locally-run frontend needs its own origin (e.g. http://localhost:3000) added to that variable in the SSO instance it's calling — typically via .env.local on a local SSO instance.
CORS Headers in SSO Responses
When your origin is in SSO_ALLOWED_ORIGINS, the SSO service includes these headers in responses:
// Example SSO Response Headers HTTP/1.1 200 OK Access-Control-Allow-Origin: https://myapp.com Access-Control-Allow-Credentials: true Access-Control-Allow-Methods: GET,POST,PUT,DELETE,OPTIONS Access-Control-Allow-Headers: Content-Type, Authorization Vary: Origin
If your origin is NOT in the allowlist, there is no distinct error response — the request is still processed by the server, and the response still comes back with a 200 (or whatever status the endpoint would normally return). The only difference is Access-Control-Allow-Origin won't match your origin, so the browser refuses to let your JavaScript read the response body. This shows up as a CORS error in the browser console, not as an HTTP error status.
Client-Side CORS Configuration
Fetch API (Recommended)
// WHY: Include credentials (cookies) in cross-origin requests
const response = await fetch('https://sso.doneisbetter.com/api/public/session', {
method: 'GET',
credentials: 'include', // REQUIRED: Sends HTTP-only cookies
headers: {
'Content-Type': 'application/json'
}
});
const data = await response.json();Axios
import axios from 'axios';
// Global configuration
axios.defaults.withCredentials = true;
// Per-request configuration
const response = await axios.get(
'https://sso.doneisbetter.com/api/public/session',
{ withCredentials: true }
);XMLHttpRequest (Legacy)
const xhr = new XMLHttpRequest();
xhr.withCredentials = true; // REQUIRED for cookies
xhr.open('GET', 'https://sso.doneisbetter.com/api/public/session');
xhr.send();Backend CORS Configuration (Your App)
If your backend needs to call SSO APIs, no CORS configuration is needed—server-to-server requests bypass CORS entirely.
However, if your frontend calls your backend, which then calls SSO, configure CORS on your backend:
Express.js
const cors = require('cors');
app.use(cors({
origin: 'https://yourfrontend.com', // Your frontend origin
credentials: true // Allow cookies
}));Next.js API Routes
// pages/api/auth/[...].js
export default function handler(req, res) {
res.setHeader('Access-Control-Allow-Origin', 'https://yourfrontend.com');
res.setHeader('Access-Control-Allow-Credentials', 'true');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
if (req.method === 'OPTIONS') {
return res.status(200).end();
}
// Handle actual request
}Common CORS Errors
Error: Browser console shows a CORS / cross-origin error
Cause: Your origin is not in the SSO deployment's SSO_ALLOWED_ORIGINS configuration, so the response comes back without a matching Access-Control-Allow-Origin header and the browser withholds it from your JavaScript.
Solution: Ask the operator of your SSO deployment to add your origin to SSO_ALLOWED_ORIGINS.
Error: "Credentials flag not set"
Cause: You're not sending credentials: 'include' in requests.
Solution: Add credentials: 'include' to fetch calls or withCredentials: true to Axios.
Error: "Preflight request failed"
Cause: OPTIONS preflight request is being blocked.
Solution: Ensure your origin is registered and you're using HTTPS (not HTTP) in production.
Testing CORS Configuration
// Test if your origin is allowed
fetch('https://sso.doneisbetter.com/api/health', {
method: 'GET',
credentials: 'include'
})
.then(response => {
console.log('CORS OK:', response.ok);
console.log('Headers:', response.headers.get('Access-Control-Allow-Origin'));
})
.catch(error => {
console.error('CORS Error:', error);
});Summary
- ☑️ Ask your SSO deployment's operator to add your origin to
SSO_ALLOWED_ORIGINS - ☑️ Always use
credentials: 'include'for API requests - ☑️ Use HTTPS in production (HTTP only for localhost development)
- ☑️ Test CORS configuration before going live