scala-http4s的 CORS中间件未添加Access-Control-Allow-Origin响应头

前端开发 2026-07-09

我正在使用http4s版本 "0.23.33" ,CORS在预检请求时无法发送 "Access-Control-Allow-Origin" ,从而阻止我的JavaScript。用curl命令尝试同样的操作,结果也是一样,没有任何头部被发送。

the CORS middleware setup:

private val corsMiddleware = CORS.policy

          .withAllowOriginHost(Set(
            Origin.Host(Uri.Scheme.http, Uri.RegName("localhost"), Some(8080))
          ))
     .withAllowHeadersAll
     .withAllowMethodsAll
     .withAllowCredentials(true) // allow broswer to store cookie
     .withMaxAge(1.day) 

val httpApp = corsMiddleware(AutoSlash(loggerService(errorLogger(routes)))).orNotFound
sending an Option Command using curl : curl -i -X OPTIONS http://localhost:4041/auth/login   -H "Origin: http://localhost:8080"   -H "Access-Control-Request-Method: POST"   -H "Access-Control-Request-Headers: Content-Type"
HTTP/1.1 200 OK
Date: Sun, 31 May 2026 18:23:08 GMT
Connection: keep-alive
Vary: Origin
Content-Length: 0

my auth routes :

val publicRoutes: HttpRoutes[F] = HttpRoutes.of[F]{
  case req @ POST -> Root / "login" => for {
    loginRequest <- req.as[LoginRequest]
    result       <- Logger[F].info("hello world---") *> authService.login(loginRequest)
    resp         <- result match {
                                    case Left(error) =>
                                      Logger[F].warn("login failed")
                                      Unauthorized(
                                        `WWW-Authenticate`(Challenge("Bearer", "Resources")),
                                        Json.obj(
                                          "Error" -> Json.fromString(error.toString)
                                        )
                                      )
                                    case Right(t) =>
                                     Ok(s"Access-token: ${t.accessToken}")
                                     .map(r => CookieService.setRefreshCookie(r, t.refreshToken, config) )
                                  }
  } yield resp
}

trying withAllowOriginAll with withAllowCredentials(false) works correctly and returns:

Connection: keep-alive
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: *
Access-Control-Allow-Headers: *
Access-Control-Max-Age: 86400
Content-Length: 0

but will block my html if credentials is included in headers which prevent Cookie storage.

all request from my frontend is being rejected because of cors even tho the domain is part of the `

.withAllowOriginHost(Set(
  Origin.Host(Uri.Scheme.http, Uri.RegName("localhost"), Some(8080))
))

it works if I allowed all origin with credentials false but then again the cookie will not be saved in browser

here is my Html served on localhost:8080 using serve command:

<!doctype html>
<html>
  <script>
    async function fetchdata() {
    try {
      const response = await fetch(
        "http://localhost:4041/auth/login",
        {
          method: "POST",
          credentials: "include", // allow cookies to be stored/sent
          headers: {
            "Content-Type": "application/json"


          },
          body: JSON.stringify({
            email: "[email protected]",
            password: "test123"
          })
        }
      );

      console.log("Status:", response.status);
      console.log("Headers:", [...response.headers.entries()]);

      if (!response.ok) {
        const errorText = await response.text();
        console.error("Login failed:", errorText);
        return;
      }

      const data = await response.json();

      console.log("Access Token:", data.accessToken);
      console.log("Refresh Token:", data.refreshToken);
      console.log("Full Response:", data);

    } catch (err) {
      console.error("Request failed:", err);
    }
  }
  </script>

  <button onclick="fetchdata();">
    Fetch
  </button>
</html>

解决方案

When withAllowCredentials(true) is set, http4s does not treat withAllowMethodsAll / withAllowHeadersAll as "any method / any header". Per the Fetch standard, * is not a wildcard once credentials are involved: it's matched literally and http4s implements exactly that. The CORSPolicy scaladoc for withAllowHeadersAll says that with credentials allowed, it only allows requests with a literal header name of *.

Allows CORS requests with any headers if credentials are not allowed. If credentials are allowed, allows requests with a literal header name of *, which is almost certainly not what you mean, but per spec.

So your effective policy permits only the literal method * and the literal header *. A real preflight asks for Access-Control-Request-Method: POST and Access-Control-Request-Headers: Content-Type; neither matches the literal *, the preflight is rejected, and http4s omits every CORS header, leaving only Vary: Origin. That is the exact response you got.

This happens regardless of whether the origin matches, which is why the Vary: Origin-only response is misleading. It looks like an origin rejection, but the request never gets that far.

This also explains why withAllowOriginAll + withAllowCredentials(false) worked for you: with credentials off, * really is a wildcard.

To fix, enumerate methods and headers whenever credentials are enabled (CIString is from org.typelevel.ci):

val corsMiddleware = CORS.policy
  .withAllowOriginHost(Set(
    Origin.Host(Uri.Scheme.http, Uri.RegName("localhost"), Some(8080))
  ))
  .withAllowHeadersIn(Set(CIString("Content-Type")))
  .withAllowMethodsIn(Set(Method.GET, Method.POST, Method.OPTIONS))
  .withAllowCredentials(true)
  .withMaxAge(1.day)

You can play around with this code here on Scastie.

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章