scalacurlhttp4scats-effecthttp4s-circe

Scala: http4s giving 401 Unauthorized for same request that works in curl/requests


I tried the following code using http4s v0.19.0:

import cats.effect._

def usingHttp4s(uri: String, bearerToken: String)(implicit cs: ContextShift[IO]): String = {
    import scala.concurrent.ExecutionContext
    import org.http4s.client.dsl.io._
    import org.http4s.headers._
    import org.http4s.Method._
    import org.http4s._
    import org.http4s.client._


    import org.http4s.client.middleware._

    val blockingEC = ExecutionContext.fromExecutorService(Executors.newFixedThreadPool(5))

    val middlewares = Seq(
      RequestLogger[IO](logHeaders = true, logBody = true, redactHeadersWhen = _ => false)(_),
      FollowRedirect[IO](maxRedirects = 5)(_)
    )

    val client = middlewares.foldRight(JavaNetClientBuilder(blockingEC).create[IO])(_.apply(_))

    val req = GET(
      Uri.unsafeFromString(uri),
      Authorization(Credentials.Token(AuthScheme.Bearer, bearerToken))
    )
    client.expect[String](req).unsafeRunSync()
  }

I get the following error:

[error] (run-main-0) org.http4s.client.UnexpectedStatus: unexpected HTTP status: 401 Unauthorized
[error] org.http4s.client.UnexpectedStatus: unexpected HTTP status: 401 Unauthorized

Not only that, my program never exited (do I have to close some client!?) and it also never printed the request even though I hooked up a logging middleware

I next tried @li-haoyi's request library and this returned no error:

def usingLiHaoyiRequest(uri: String, bearerToken: String): String =
    requests.get(uri, headers = Iterable("Authorization" -> s"Bearer $bearerToken")).text()

The above code works for the same uri and same baseToken so it can't be that my token is wrong. To be doubly sure, I tried curl:

curl -L -H "Authorization: Bearer ${BEARER}" ${URI}

This problem also happens in http4s v0.18.19 (Even with explicit Json and accept headers):

import io.circle.Json

def usingHttp4s(uri: String, bearerToken: String): Json = {
    import org.http4s.client.dsl.io._
    import org.http4s.headers._
    import org.http4s.Method._
    import org.http4s._
    import org.http4s.client.blaze.Http1Client
    import org.http4s.client.middleware._
    import org.http4s.circe._
    import org.http4s.MediaType._

    val program = for {
      c <- Http1Client[IO]()
      client = FollowRedirect(maxRedirects = 5)(c)
      req = GET(
        Uri.unsafeFromString(uri),
        Authorization(Credentials.Token(AuthScheme.Bearer, bearerToken)),
        Accept(`application/json`)
      )
      res <- client.expect[Json](req)
    } yield res

    program.unsafeRunSync()
  }

So my question is:

  1. How come both requests and curl work but http4s gives me 401 for same request?
  2. Why does my http4s version never exit?
  3. Why does the request logger middleware does not log request?

Solution

  • As described in the gitter room, the bug is in http4s which does not forward authorization header to redirects but both curl and requests do (as long as forwarding is to same sub-domain).