Akka HTTP: Credentials are not propagated to authenticateBasic method

40 views Asked by At

I have the following Akka directive structure

cors(){
   post {
      extractCredentials {
         credentials:Option[HttpCredentials] => {
            // I can successfully inspect the credentials here
         }
      }
   }
}

Now I am trying to use authenticateBasic like


private def foo(credentials: Credentials):Option[FineAuthenticationResult] = {
      credentials match
        case Credentials.Provided(token) =>
          println(token)
          Some(ValidCredentials("foo"))
        case _ => Some(InvalidToken("foo"))
}

cors(){
   post {
       authenticateBasic(
                  realm = "Secure",
                  foo
                ) { authenticationResult =>
                  // Keep track of authorization results per channel and EVENT type
                  val additionalTags = Map(
                    "eventType" -> event.customEventType,
                    "channel" -> event.channel
                  )
                  authenticationResult match
                    case InvalidToken(message) =>
                      reject(CredentialsRejection(s"invalid token $message", additionalTags))
                    case InvalidCredentials(message) =>
                      reject(CredentialsRejection(s"invalid token $message", additionalTags))
                    case ValidCredentials(_) => complete(StatusCode.int2StatusCode(200))
                }
   }
}

The foo authenticator always ends up in the Missing credentials case. I am testing like

      Postman.post(uri = "/path/path",
        channel = Some("a_channel"),
        eventType = Some("a-request")) ~>
        addCredentials(getValidToken) ~>
        routes ~>
        check {
          status shouldBe StatusCodes.OK
        }

Why am I always getting missing credentials when using authenticateBasic while using extractCredentials always gives me the right credentials.

1

There are 1 answers

0
Niko On

tldr

The problem is really simple but I had to go through the internals of authenticateBasic to understand what is going wrong. We cannot use authenticateBasic with an OAuth2Token. We can only use authenticateOAuth2.

More details

authenticateBasic is using

  def authenticateBasicAsync[T](realm: String, authenticator: AsyncAuthenticator[T]): AuthenticationDirective[T] =
    extractExecutionContext.flatMap { implicit ec =>
      authenticateOrRejectWithChallenge[BasicHttpCredentials, T] { cred =>
        authenticator(Credentials(cred)).fast.map {
          case Some(t) => AuthenticationResult.success(t)
          case None    => AuthenticationResult.failWithChallenge(HttpChallenges.basic(realm))
        }
      }
    }

under the hood. And authenticateBasicAsync is using

  def authenticateOrRejectWithChallenge[C <: HttpCredentials: ClassTag, T](
    authenticator: Option[C] => Future[AuthenticationResult[T]]): AuthenticationDirective[T] =
    extractCredentialsAndAuthenticateOrRejectWithChallenge(extractCredentials.map(_ collect { case c: C => c }), authenticator)

Here we can see that C is in principle BasicHttpCredentials. But with addCredentials - in the case of OAuth0 - C will be OAuth0Token. Consequently, this collect

_ collect { case c: C => c }

is going to drop the Credentials object.