5.5 CORS, trusted vs. allowed origins, and the CSRF guard
If a browser app on a different origin needs to call your tenant, you configure CORS — per tenant, enforced by the host. RS2 distinguishes two trust levels, because they imply different, security-sensitive cookie behavior.
The config
"cors": {
"trustedOrigins": ["https://app.acme.com", "*.acme.dev"],
"allowedOrigins": ["https://reader.example"]
}
With no cors block, no CORS headers are sent — the tenant is API-only (callable
by non-browser clients, not by cross-origin browser JS). Same-origin requests
(the Origin host equals the Host) never involve CORS at all.
Origin patterns: a full origin (https://app.acme.com, scheme-exact), a bare
hostname (any scheme/port), *.suffix (the apex included), or *.
Trusted vs. allowed — the key distinction
| Trusted origins | Allowed origins | |
|---|---|---|
| CORS | Credentialed (Allow-Credentials: true, origin echoed) |
Plain, no credentials |
| Cookies | Login sets rs-auth cookie SameSite=None; Secure |
Login sets no cookie |
| Auth lane | Cookie or bearer | Bearer only (the body token) |
| Use it for | Your own first-party apps | Third-party / less-trusted browser apps |
The reasoning: a trusted origin is one you control, so it's safe to hand it a cross-site cookie. An allowed origin gets a strictly weaker deal — it can call the API from a browser, but it must carry the bearer token itself and can't ride a cookie. Choose the lower tier unless the app is genuinely first-party.
Preflights and exposed headers
The host answers CORS preflights (OPTIONS + Origin +
Access-Control-Request-Method) from permitted origins with 204 before
routing — your services never see them. The response exposes the headers
clients need: ETag, Location, Link, X-Total-Count, X-Trace-Id,
Idempotency-Replayed, Retry-After. Error responses are decorated with CORS
headers too.
The CSRF guard (always on)
Independent of your CORS config, an always-on guard rejects a
cookie-authenticated unsafe request (POST/PUT/PATCH/DELETE) coming from a
cross-site origin that isn't in trustedOrigins — 403, before routing.
Bearer-authenticated requests are unaffected.
This is what makes the cookie lane safe: a malicious site can't silently use a
victim's rs-auth cookie to perform a write, because it isn't a trusted origin.
Optionally restricting where login can be called from
auth.allowedLoginOrigins (in the auth settings), when non-empty, requires
cross-origin login/refresh calls to come from a listed origin (403 otherwise);
same-origin is always allowed. Use it to keep credential submission to known
front-ends.
Picking a posture
- First-party SPA, same origin as the API: no
corsneeded. - First-party SPA on another origin: put it in
trustedOrigins; use the cookie or bearer. - Third-party browser integration: put it in
allowedOrigins; it uses bearer tokens only.
That completes auth and access. Part 6 adds reading your data back selectively with stored queries.
← Previous: 5.4 Role specs on mounts · Manual home · Next: Part 6 — 6.1 The query service →