#5508·casdoor

[Feature] Make CORS `Access-Control-Allow-Headers` configurable via `app.conf`

Author: Neko1313Created May 24, 2026Updated May 24, 2026

[Feature] Make CORS Access-Control-Allow-Headers configurable via app.conf

Problem

Access-Control-Allow-Headers in routers/cors_filter.go:37 is hardcoded:

go
ctx.Output.Header(headerAllowHeaders, "Content-Type, Authorization")

Any cross-origin request that includes a non-safelisted header outside Content-Type / Authorization triggers a CORS preflight that the browser rejects, because Casdoor's OPTIONS response doesn't list the header in Access-Control-Allow-Headers.

This blocks legitimate integrations and forces every team to choose between:

  • Wrapping Casdoor behind a reverse proxy that rewrites the headers (the workaround in #3691).
  • Patching client code to strip the headers (the workaround we ended up using for Swagger UI).
  • Forking Casdoor.

Reproduction

The clearest case is Swagger UI calling the OIDC token endpoint during an authorizationCode flow:

Stack: FastAPI backend, mounted Swagger UI with OAuth2AuthorizationCodeBearer + usePkceWithAuthorizationCodeGrant: true, Casdoor as OIDC provider.

Setup:

  1. init_data.json has an application with clientId: "demo" and redirectUris: ["http://localhost:8000/api/v1/docs/oauth2-redirect"].
  2. The FastAPI security scheme is configured from Casdoor's .well-known/openid-configuration:
    • authorization_endpoint: http://localhost:8001/login/oauth/authorize
    • token_endpoint: http://localhost:8001/api/login/oauth/access_token
  3. Browser is on http://localhost:8000/api/v1/docs.

Steps:

  1. Click Authorize in Swagger UI → enter clientId → submit.
  2. Browser redirects to Casdoor /login/oauth/authorize, you sign in.
  3. Casdoor redirects back to …/docs/oauth2-redirect?code=….
  4. Swagger UI tries to POST http://localhost:8001/api/login/oauth/access_token with PKCE code_verifier.

Expected: Token returned, Swagger UI saves Bearer token.

Actual:

Browser console:

Access to fetch at 'http://localhost:8001/api/login/oauth/access_token' from origin
'http://localhost:8000' has been blocked by CORS policy: Request header field
x-requested-with is not allowed by Access-Control-Allow-Headers in preflight response.

The OPTIONS preflight succeeds with HTTP 200 (verified via curl), Casdoor returns:

Access-Control-Allow-Origin: http://localhost:8000
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true

X-Requested-With: XMLHttpRequest is added by Swagger UI's bundled fetch client (a long-standing legacy behavior that cannot be disabled from configuration). The browser blocks the actual POST because X-Requested-With isn't in the allow-list.

Why this is broader than Swagger UI

Many HTTP clients send X-Requested-With automatically:

  • Swagger UI 3.x and 5.x (bundled inside FastAPI, Drf-Spectacular, Spring Boot's Swagger integration, etc.).
  • jQuery $.ajax.
  • Axios with certain interceptors.
  • Older XMLHttpRequest-based code.

Other common non-safelisted headers that integrators want to add:

  • X-CSRF-Token (when fronting Casdoor with apps that use CSRF tokens).
  • X-Trace-Id / X-Request-Id (observability stacks).
  • If-Modified-Since, Cache-Control (caching layers — see the nginx workaround in #3691).

Today every integrator has to either run Casdoor behind a reverse proxy or patch their client. A one-line config would solve it for everyone.

Proposed solution

Add an optional corsAllowHeaders key to app.conf with the current value as default. Read it in setCorsHeaders.

conf/app.conf — add:

ini
# Comma-separated list of headers added to Access-Control-Allow-Headers.
# Default: "Content-Type, Authorization"
corsAllowHeaders = "Content-Type, Authorization"

routers/cors_filter.go — change setCorsHeaders:

diff
 func setCorsHeaders(ctx *context.Context, origin string) {
     ctx.Output.Header(headerAllowOrigin, origin)
     ctx.Output.Header(headerAllowMethods, "POST, GET, OPTIONS, DELETE")
-    ctx.Output.Header(headerAllowHeaders, "Content-Type, Authorization")
+    allowHeaders := conf.GetConfigString("corsAllowHeaders")
+    if allowHeaders == "" {
+        allowHeaders = "Content-Type, Authorization"
+    }
+    ctx.Output.Header(headerAllowHeaders, allowHeaders)
     ctx.Output.Header(headerAllowCredentials, "true")

     if ctx.Input.Method() == "OPTIONS" {
         ctx.ResponseWriter.WriteHeader(http.StatusOK)
     }
 }

That preserves backward compatibility (existing deployments behave exactly as before) and lets operators opt in to whatever set of headers their stack needs.

Same idea, optionally bigger

If the maintainers prefer a richer config surface, the same pattern could expose:

  • corsAllowMethods (also hardcoded to POST, GET, OPTIONS, DELETE).
  • corsAllowCredentials (true hardcoded).
  • corsExposeHeaders (currently not sent at all).

Happy to send a PR for the minimal corsAllowHeaders change first if the direction is acceptable.

Related

  • #3691 — nginx-level workaround that adds X-Requested-With and others to the allow-list.
  • #1408 — earlier CORS report; closed without addressing allow-headers.
  • swagger-api/swagger-ui#6081 — long-standing Swagger UI CORS issue, downstream of every OAuth provider that doesn't accept X-Requested-With.

Environment

  • Casdoor latest Docker image (verified against current master routers/cors_filter.go).
  • Browsers: Chrome 134+, Firefox 124+, Safari 17+ (all enforce preflight strictly per CORS spec).
  • Use case: any OIDC integration whose client library adds non-safelisted headers — confirmed with FastAPI's bundled Swagger UI but applies to any HTTP client emitting X-Requested-With or similar.