AUTH4D-85 CORS for discovery, JWKS and UserInfo

The browser SDK reads tenant discovery, the JWKS and UserInfo from the page’s own origin. Before this change these paths only accepted origins on the deployment-wide CLIENT_ORIGINS list, so a browser application’s registered Allowed origins were not enough to sign in. Now a registered origin is enough. Only guest sign-in (POST /t/{tenantId}/guest) still needs CLIENT_ORIGINS.

Discovery and JWKS: public CORS

GET /t/{tenantId}/.well-known/openid-configuration and GET /t/{tenantId}/.well-known/jwks.json are public metadata:

  • Every response, including errors, carries Access-Control-Allow-Origin: * and never Access-Control-Allow-Credentials. The wildcard is literal; the request origin is never reflected, so browsers refuse the response to credentialed requests.
  • Any Origin is accepted.
  • Preflights (OPTIONS) are answered with 204 for GET, with at most the Authorization, Content-Type and X-Requested-With request headers. Other methods or headers get 403.

UserInfo: per-client CORS

GET /oauth/userinfo decides CORS per client, the same way as /oauth/token and /oauth/revoke (AUTH4D-63 (repository file docs/api-spec/auth4d-63.md)), instead of using CLIENT_ORIGINS:

  • With a valid bearer access token and an Origin header, the response is readable only when the token’s client (looked up inside the token’s tenant) is an enabled browser application with that origin in its allowedOrigins. The response then carries Access-Control-Allow-Origin: {origin} and Vary: Origin, never Access-Control-Allow-Credentials.
  • Any other origin, including another application of the same tenant and the console origin, gets 403 invalid_request (“The browser origin is not registered.”) without CORS headers and without claims.
  • An invalid or expired token gets 401 without CORS headers, because no client can be trusted from it; the SDK reports userinfo_failed either way.
  • Requests without an Origin header (servers, native games) are unchanged.

Preflight

A preflight cannot see the bearer token, so the API cannot know the token’s client. The UserInfo preflight is therefore answered with 204, Access-Control-Allow-Origin: {origin}, Vary: Origin, Access-Control-Allow-Methods: GET and Access-Control-Allow-Headers: authorization when:

  • the origin is a canonical HTTPS (or loopback HTTP) origin,
  • the request method is GET and the only requested header is Authorization, and
  • the origin is registered on any enabled browser application of the active customer tenant named by the tenant_id query parameter (discovery publishes the UserInfo endpoint with it).

Anything else gets 403 origin_denied without CORS headers. The preflight only lets the browser send the request; the actual response still enforces the token’s own client as described above, so tenant_id alone never decides who may read UserInfo.

Unchanged

  • Management routes keep the console-only, credentialed CORS policy.
  • The console’s backend-for-frontend is same-origin and unaffected.
  • /oauth/authorize, hosted-login routes, device routes and guest sign-in keep the deployment-wide CLIENT_ORIGINS policy.

Source: docs/api-spec/auth4d-85.md