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 otherAccess-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-Originfor 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-Originfor 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.
Related
- 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.
Rate limits
SenseCrypt rate-limit policy for the protocol endpoints you integrate against — the per-action windows and ceilings you need to design retries, the 429 response shape with Retry-After, how the client IP is determined, and how to handle limits.
Validate a SenseCrypt token
The canonical recipe for validating a SenseCrypt JWT — fetch the tenant JWKS from jwks_uri, select the key by kid, verify the ES256 signature, and check iss, aud, exp, and token_use, with JWKS caching and key-rotation handling.