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 trustedOrigins403, 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 cors needed.
  • 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 →