Allow Multiple Specific Origins
Handle CORS when multiple frontend domains need access. Learn the server-side pattern for dynamically setting Allow-Origin from an allowlist.
Detailed Explanation
Dynamic Origin from an Allowlist
The Access-Control-Allow-Origin header accepts only one value (or *). You cannot comma-separate multiple origins. When you need to allow https://app.example.com and https://admin.example.com, the server must dynamically check the incoming Origin header against an allowlist and echo it back if it matches.
Server Logic (Pseudocode)
allowed = ["https://app.example.com", "https://admin.example.com"]
if request.headers["Origin"] in allowed:
response.headers["Access-Control-Allow-Origin"] = request.headers["Origin"]
response.headers["Vary"] = "Origin"
Nginx Implementation
map $http_origin $cors_origin {
default "";
"https://app.example.com" "$http_origin";
"https://admin.example.com" "$http_origin";
}
if ($cors_origin != "") {
add_header Access-Control-Allow-Origin $cors_origin always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
add_header Access-Control-Allow-Headers "Content-Type, Authorization" always;
add_header Access-Control-Allow-Credentials "true" always;
add_header Vary Origin always;
}
Express.js Implementation
const allowedOrigins = [
"https://app.example.com",
"https://admin.example.com",
];
app.use(cors({
origin: (origin, callback) => {
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error("Not allowed by CORS"));
}
},
credentials: true,
}));
Why Vary: Origin Is Critical
Because the response changes depending on which origin sent the request, caches must know to key on the Origin header. Without Vary: Origin, a CDN could cache the response for app.example.com and serve it to admin.example.com, which would fail the browser's origin check.
Use Case
A company with separate frontend apps for customers (app.example.com) and internal staff (admin.example.com), both consuming the same REST API. Each origin needs to be explicitly whitelisted.