authenticateBasicAsync

Signature

type AsyncAuthenticator[T] = Credentials => Future[Option[T]]
def authenticateBasicAsync[T](realm: String, authenticator: AsyncAuthenticator[T]): AuthenticationDirective[T] 

Description

Wraps the inner route with Http Basic authentication support using a given AsyncAuthenticator[T] AsyncAuthenticator<T> - function from Optional<ProvidedCredentials> to CompletionStage<Optional<T>>.

This variant of the authenticateBasic directive returns a Future[Option[T]]CompletionStage<Optional<T>> which allows freeing up the routing layer of Apache Pekko HTTP, freeing it for other requests. It should be used whenever an authentication is expected to take a longer amount of time (e.g. looking up the user in a database).

In case the returned option is Nonean empty Optional the request is rejected with a AuthenticationFailedRejectionAuthenticationFailedRejection, which by default is mapped to an 401 Unauthorized response.

Standard HTTP-based authentication which uses the WWW-Authenticate header containing challenge data and AuthorizationAuthorization header for receiving credentials is implemented in authenticateOrRejectWithChallenge.

See Credentials and password timing attacks for details about verifying the secret.

Warning

Make sure to use basic authentication only over SSL/TLS because credentials are transferred in plaintext.

Example

Scala
sourcedef myUserPassAuthenticator(credentials: Credentials): Future[Option[String]] =
  credentials match {
    case p @ Credentials.Provided(id) =>
      Future {
        // potentially
        if (p.verify("p4ssw0rd")) Some(id)
        else None
      }
    case _ => Future.successful(None)
  }

val route =
  Route.seal {
    path("secured") {
      authenticateBasicAsync(realm = "secure site", myUserPassAuthenticator) { userName =>
        complete(s"The user is '$userName'")
      }
    }
  }

// tests:
Get("/secured") ~> route ~> check {
  status shouldEqual StatusCodes.Unauthorized
  responseAs[String] shouldEqual "The resource requires authentication, which was not supplied with the request"
  header[`WWW-Authenticate`].get.challenges.head shouldEqual HttpChallenge("Basic", Some("secure site"),
    Map("charset" -> "UTF-8"))
}

val validCredentials = BasicHttpCredentials("John", "p4ssw0rd")
Get("/secured") ~> addCredentials(validCredentials) ~> // adds Authorization header
route ~> check {
  responseAs[String] shouldEqual "The user is 'John'"
}

val invalidCredentials = BasicHttpCredentials("Peter", "pan")
Get("/secured") ~>
addCredentials(invalidCredentials) ~> // adds Authorization header
route ~> check {
  status shouldEqual StatusCodes.Unauthorized
  responseAs[String] shouldEqual "The supplied authentication is invalid"
  header[`WWW-Authenticate`].get.challenges.head shouldEqual HttpChallenge("Basic", Some("secure site"),
    Map("charset" -> "UTF-8"))
}
Java
sourceimport org.apache.pekko.http.javadsl.server.directives.SecurityDirectives.ProvidedCredentials;

import static org.apache.pekko.http.javadsl.server.Directives.authenticateBasicAsync;
import static org.apache.pekko.http.javadsl.server.Directives.complete;
import static org.apache.pekko.http.javadsl.server.Directives.path;

final Function<Optional<ProvidedCredentials>, CompletionStage<Optional<String>>>
    myUserPassAuthenticator =
        opt -> {
          if (opt.filter(c -> (c != null) && c.verify("p4ssw0rd")).isPresent()) {
            return CompletableFuture.completedFuture(Optional.of(opt.get().identifier()));
          } else {
            return CompletableFuture.completedFuture(Optional.empty());
          }
        };

final Route route =
    path(
            "secured",
            () ->
                authenticateBasicAsync(
                    "secure site",
                    myUserPassAuthenticator,
                    userName -> complete("The user is '" + userName + "'")))
        .seal();

// tests:
testRoute(route)
    .run(HttpRequest.GET("/secured"))
    .assertStatusCode(StatusCodes.UNAUTHORIZED)
    .assertEntity(
        "The resource requires authentication, which was not supplied with the request")
    .assertHeaderExists("WWW-Authenticate", "Basic realm=\"secure site\",charset=UTF-8");

final HttpCredentials validCredentials =
    BasicHttpCredentials.createBasicHttpCredentials("John", "p4ssw0rd");
testRoute(route)
    .run(HttpRequest.GET("/secured").addCredentials(validCredentials))
    .assertEntity("The user is 'John'");

final HttpCredentials invalidCredentials =
    BasicHttpCredentials.createBasicHttpCredentials("Peter", "pan");
testRoute(route)
    .run(HttpRequest.GET("/secured").addCredentials(invalidCredentials))
    .assertStatusCode(StatusCodes.UNAUTHORIZED)
    .assertEntity("The supplied authentication is invalid")
    .assertHeaderExists("WWW-Authenticate", "Basic realm=\"secure site\",charset=UTF-8");