Scala, ZIO, Tapir - how to retrieve authorization token from header?

632 views Asked by At

I have created tapir endpoint:

val getEndpoint = endpoint.get
  .securityIn(auth.bearer[String]())
  .in("players" / path[PlayerId]("playerId"))
  .in(query[PlayerRequest]("query"))
  .errorOut(someErrors)    

Now, I would like to read all passed values: bearer token, playerId and query. So I created ZIO server logic:

PlayersEndpoint.getEndpoint.zServerLogic { case (playerId, query) => 
  //some logic to do...
}

It works fine, but whithout bearer token. Here I could not read bearer token. I tried to change it to something like:

    PlayersEndpoint.getEndpoint.zServerSecurityLogic{ case (token) => 
      //do smth with token 
     }.zServerLogic { case (playerId, query) => 
          //some logic to do...
        }

But it did not work. I would like to read all 3 values and decide about what to do after checking token. Docs and examples are very poor and do not show how to read tokens from tapir. Do you know how I should do it correctly?

2

There are 2 answers

2
counter2015 On

You can try this:

import org.http4s.blaze.server.BlazeServerBuilder
import org.http4s.server.Router
import sttp.tapir.server.http4s.ztapir.ZHttp4sServerInterpreter
import sttp.tapir.ztapir._
import zio._
import zio.interop.catz._
import cats.implicits._


object TapirExample extends ZIOAppDefault {
  type Env = Any

  // fake type
  type PlayerId = Int
  type PlayerRequest = Long

  def authLogic(token: String): ZIO[Any, String, String] = {
    if (token != "secret") ZIO.fail("user not login")
    else ZIO.succeed(token)
  }

  val authEndpoint: ZPartialServerEndpoint[Any, String, String, Unit, String, Unit, Any] =
    endpoint
      .securityIn(auth.bearer[String]())
      .errorOut(stringBody)
      .zServerSecurityLogic(authLogic)

  val getEndpoint =
    authEndpoint
      .get
      .in("players" / path[PlayerId]("playerId"))
      .in(query[PlayerRequest]("query"))
      .out(stringBody)
      .serverLogic(token => queryTuple => getLogic(token, queryTuple._1, queryTuple._2))

  val getRoute =
    ZHttp4sServerInterpreter()
      .from(
        List(
          getEndpoint.widen[Env]
        )
      ).toRoutes

  def getLogic(token: String, playerId: PlayerId, request: PlayerRequest): ZIO[Any, Nothing, String] = {
    ZIO.succeed(
      s"""
        | token: $token
        | id: $playerId
        | request: $request
        |""".stripMargin)
  }

  override def run: ZIO[Any with ZIOAppArgs with Scope, Any, Any] = {


    val io = ZIO.runtime[Env].flatMap { _ =>
      for {
        _ <- ZIO.executor.flatMap(executor =>
          BlazeServerBuilder[ZIO[Env, Throwable, *]]
            .withExecutionContext(executor.asExecutionContext)
            .bindHttp(9090, "0.0.0.0")
            .withHttpApp(
              Router(
                "/" -> (
                  getRoute
                )
              ).orNotFound
            )
            .serve
            .compile
            .drain)
      } yield ()
    }

    io

  }
}

And the library version is

ThisBuild / version := "0.1.0-SNAPSHOT"

ThisBuild / scalaVersion := "2.13.8"

lazy val root = (project in file("."))
  .settings(
    name := "playground"
  )

val tapirVersion = "1.2.4"

val allDependency = Seq(
  "com.softwaremill.sttp.tapir" %% "tapir-http4s-server-zio" % tapirVersion,
  "org.http4s" %% "http4s-blaze-server"      % "0.23.13",
)

libraryDependencies ++= allDependency

addCompilerPlugin("org.typelevel" %% "kind-projector" % "0.13.2" cross CrossVersion.full)

Test by curl

$ curl --location --request GET 'localhost:9090/players/233?query=334' \
--header 'Authorization: Bearer wrongtoken'
user not login

$ curl --location --request GET 'localhost:9090/players/233?query=334' \
--header 'Authorization: Bearer secret'

 token: secret
 id: 233
 request: 334

refer:

0
Gaël J On

I don't have IDE to give you the exact code but the idea is that the return of your zServerSecurityLogic can be used in the following zServerLogic.

Usually you'd validate the token in the zServerSecurityLogic and return some kind of User value that you can use as input in zServerLogic.

But you can also "do nothing" in the security logic and just passthrough the token so that it's available in the main logic.