authenticateBasicPFAsync

Signature

type AsyncAuthenticatorPF[T] = PartialFunction[Credentials, Future[T]]
def authenticateBasicPFAsync[T](realm: String, authenticator: AsyncAuthenticatorPF[T]): AuthenticationDirective[T] 

Description

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

Provides support for handling HTTP Basic Authentication.

Refer to authenticateBasic for a detailed description of this directive.

Its semantics are equivalent to authenticateBasicPF ’s, where not handling a case in the Partial Function (PF) leaves the request to be rejected with a AuthenticationFailedRejectionAuthenticationFailedRejection rejection.

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
sourcecase class User(id: String)
def fetchUser(id: String): Future[User] = {
  // some fancy logic to obtain a User
  Future.successful(User(id))
}

val myUserPassAuthenticator: AsyncAuthenticatorPF[User] = {
  case p @ Credentials.Provided(id) if p.verify("p4ssw0rd") =>
    fetchUser(id)
}

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

// 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.authenticateBasicPFAsync;
import static org.apache.pekko.http.javadsl.server.Directives.complete;
import static org.apache.pekko.http.javadsl.server.Directives.path;

class User {
  private final String id;

  public User(String id) {
    this.id = id;
  }

  public String getId() {
    return id;
  }
}

final PartialFunction<Optional<ProvidedCredentials>, CompletionStage<User>>
    myUserPassAuthenticator =
        new JavaPartialFunction<Optional<ProvidedCredentials>, CompletionStage<User>>() {
          @Override
          public CompletionStage<User> apply(Optional<ProvidedCredentials> opt, boolean isCheck)
              throws Exception {
            if (opt.filter(c -> (c != null) && c.verify("p4ssw0rd")).isPresent()) {
              if (isCheck) return CompletableFuture.completedFuture(null);
              else return CompletableFuture.completedFuture(new User(opt.get().identifier()));
            } else {
              throw noMatch();
            }
          }
        };

final Route route =
    path(
            "secured",
            () ->
                authenticateBasicPFAsync(
                    "secure site",
                    myUserPassAuthenticator,
                    user -> complete("The user is '" + user.getId() + "'")))
        .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");