Security

CORS Configuration

Allowed origin rules, browser behavior, and safe expectations for cross-origin SSO consumers.

SSO Version

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_ORIGINS configuration (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:

  1. Determine your application's origin(s) (e.g., https://myapp.com) — must be HTTPS in production
  2. Ask the operator of your SSO deployment to add it to SSO_ALLOWED_ORIGINS and 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