Question Details

No question body available.

Tags

scala cors access-control http4s

Answers (1)

May 31, 2026 Score: 2 Rep: 13,709 Quality: Medium Completeness: 80%

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.