SenseCrypt Docs
Reference

CORS

How cross-origin browser access to SenseCrypt works — the backend registers no CORS middleware of its own, so CORS is terminated at the operator's reverse proxy, and confidential apps should prefer server-side token exchange.

If a browser-based app (a single-page app talking to SenseCrypt directly from JavaScript) calls a SenseCrypt endpoint from a different origin — for example, exchanging an authorization code at the token_endpoint — the browser first enforces the Cross-Origin Resource Sharing (CORS) protocol. This page explains where CORS is handled in a SenseCrypt deployment and how to avoid needing it at all.

The short version

  • The SenseCrypt backend does not register any CORS middleware of its own. It emits no Access-Control-Allow-Origin (or other Access-Control-*) headers from the application itself.
  • CORS is therefore a deployment concern, terminated at the operator's reverse proxy in front of the tenant — not something the application configures per client.
  • Confidential apps should avoid CORS entirely by doing the token exchange server-side, or by serving the SPA same-origin with the tenant. This is both simpler and more secure.

Why the app emits no CORS headers

Server-to-server callers — a confidential web app's backend, an API validating a bearer token, a machine-to-machine client — are not browsers and are not subject to CORS. The same-origin policy is a browser mechanism, so these callers never send a CORS preflight and never look for Access-Control-* response headers. For SenseCrypt's core audience (confidential OIDC clients and APIs), CORS simply does not enter the picture.

CORS only matters when JavaScript in a browser makes a cross-origin request to a tenant host — most commonly a public SPA doing the Authorization Code + PKCE code exchange directly against the token_endpoint. Because whether (and how) that is allowed depends on the deployment's edge, SenseCrypt leaves the decision to the layer that owns it: the reverse proxy.

Where CORS is handled: the reverse proxy

Every SenseCrypt deployment sits behind a reverse proxy that terminates TLS and forwards the original host to the app (this is how per-tenant issuers are routed — see Multi-tenancy and Self-hosting). That proxy is the natural — and correct — place to apply a CORS policy: it can add Access-Control-Allow-Origin for the specific SPA origins that are permitted, and answer OPTIONS preflight requests, without changing the application.

What this means in practice:

  • For a browser-only SPA that exchanges codes at the tenant host, the tenant's edge must return Access-Control-Allow-Origin for your SPA's exact origin (scheme + host + port). If it doesn't, the browser blocks the response and you see a CORS error even though the request itself was well-formed.
  • The policy is operator-controlled and environment-specific. There is no per-application "allowed web origins" setting in SenseCrypt that emits these headers for you — the allow-list lives in the proxy configuration. If you hit a CORS error against a hosted tenant, ask your SenseCrypt operator whether your SPA origin is allowed at the edge.

The better alternative: don't need CORS

CORS is only in play when browser JavaScript talks cross-origin to the tenant. You can design that away:

Confidential apps: exchange tokens server-side

If your app has any backend, do the OIDC code exchange on the server and keep tokens out of the browser. The Next.js and Node/Express quickstarts follow this confidential-client pattern. The server-to-server call to the token_endpoint is not a browser request, so CORS never applies, and — because your client_secret and the resulting tokens never reach the browser — this is the stronger security posture regardless of CORS. This is the recommended approach whenever you have somewhere to run server code.

SPAs: serve same-origin, or accept the edge dependency

For a genuinely backend-less SPA, you have two clean options:

  • Serve the SPA same-origin with the tenant host so requests to SenseCrypt are same-origin and CORS is moot.
  • Otherwise, treat the CORS allow-list as part of your deployment: confirm with your operator that your SPA's origin is permitted at the proxy. Combine it with the PKCE public-client guidance in the React SPA quickstart (short-lived access tokens; avoid long-lived refresh tokens in the browser).

Troubleshooting

  • CORS error on the token endpoint from a SPA. The request reached the tenant, but the edge didn't return Access-Control-Allow-Origin for your origin. Confirm your SPA origin is allow-listed at the proxy, serve the SPA same-origin, or move the exchange server-side.
  • No CORS error from a backend, "why?" Server-to-server calls aren't browser requests, so CORS doesn't apply — this is expected. A confidential client should never depend on CORS.
  • It works in one environment but not another. CORS policy lives in each deployment's proxy configuration, so it can differ between environments. Verify the policy for the specific tenant you're calling.
  • React SPA quickstart — the browser-only public-client flow where CORS can surface.
  • Node/Express quickstart — the confidential, server-side-exchange pattern that avoids CORS.
  • Self-hosting — the reverse proxy that fronts every deployment and owns the CORS policy.
  • Multi-tenancy — why every tenant is fronted by a host-routing proxy.

On this page