authenticate
Authenticates a request by checking credentials supplied in the request and extracts a value representing the authenticated principal.
Signature
def authenticate[T](auth: ⇒ Future[Authentication[T]])(implicit executor: ExecutionContext): Directive1[T]
def authenticate[T](auth: ContextAuthenticator[T])(implicit executor: ExecutionContext): Directive1[T]
The signature shown is simplified, the real signature uses magnets. [1]
[1] | See The Magnet Pattern for an explanation of magnet-based overloading. |
Description
On the lowest level, authenticate
, takes either a Future[Authentication[T]]
which
authenticates based on values from the lexical scope or a value of type
ContextAuthenticator[T] = RequestContext ⇒ Future[Authentication[T]]
which
extracts authentication data from the RequestContext
. The returned value of type Authentication[T]
must
either be the authenticated principal which will be supplied to the inner route or a rejection to reject the request
with if authentication failed .
Both variants return futures so that the actual authentication procedure runs detached from route processing
and processing of the inner route will be continued once the authentication finished. This
allows longer-running authentication tasks (like looking up credentials in a database) to run without blocking
the HttpService
actor, freeing it for other requests. The authenticate
directive itself
isn’t tied to any HTTP-specific details so that various authentication schemes can be implemented
on top of authenticate
.
Standard HTTP-based authentication which uses the WWW-Authenticate
header containing challenge
data and Authorization
header for receiving credentials is implemented in subclasses of HttpAuthenticator
.
HTTP Basic Authentication
spray supports HTTP basic authentication through the BasicHttpAuthenticator
and provides a series of
convenience constructors for different scenarios with BasicAuth()
. Make sure to use basic authentication only over
SSL because credentials are transferred in plaintext.
Implementing a UserPassAuthenticator
The most generic way of deploying HTTP basic authentication uses a UserPassAuthenticator
to validate a user/password
combination. It is defined like this:
type UserPassAuthenticator[T] = Option[UserPass] ⇒ Future[Option[T]]
Its job is to map a user/password combination (if existent in the request) to an authenticated custom principal of type
T
(if authenticated).
def myUserPassAuthenticator(userPass: Option[UserPass]): Future[Option[String]] =
Future {
if (userPass.exists(up => up.user == "John" && up.pass == "p4ssw0rd")) Some("John")
else None
}
val route =
sealRoute {
path("secured") {
authenticate(BasicAuth(myUserPassAuthenticator _, realm = "secure site")) { userName =>
complete(s"The user is '$userName'")
}
}
}
Get("/secured") ~> route ~> check {
status === StatusCodes.Unauthorized
responseAs[String] === "The resource requires authentication, which was not supplied with the request"
header[HttpHeaders.`WWW-Authenticate`].get.challenges.head === HttpChallenge("Basic", "secure site")
}
val validCredentials = BasicHttpCredentials("John", "p4ssw0rd")
Get("/secured") ~>
addCredentials(validCredentials) ~> // adds Authorization header
route ~> check {
responseAs[String] === "The user is 'John'"
}
val invalidCredentials = BasicHttpCredentials("Peter", "pan")
Get("/secured") ~>
addCredentials(invalidCredentials) ~> // adds Authorization header
route ~> check {
status === StatusCodes.Unauthorized
responseAs[String] === "The supplied authentication is invalid"
header[HttpHeaders.`WWW-Authenticate`].get.challenges.head === HttpChallenge("Basic", "secure site")
}
From configuration
There are several overloads to configure users from the configuration file. Obviously, this is neither a secure
(plaintext passwords) nor a scalable approach. If you don’t pass in a custom config users are configured from
the Configuration path spray.routing.users
.
def extractUser(userPass: UserPass): String = userPass.user
val config = ConfigFactory.parseString("John = p4ssw0rd")
val route =
sealRoute {
path("secured") {
authenticate(BasicAuth(realm = "secure site", config = config, createUser = extractUser _)) { userName =>
complete(s"The user is '$userName'")
}
}
}
Get("/secured") ~> route ~> check {
status === StatusCodes.Unauthorized
responseAs[String] === "The resource requires authentication, which was not supplied with the request"
header[HttpHeaders.`WWW-Authenticate`].get.challenges.head === HttpChallenge("Basic", "secure site")
}
val validCredentials = BasicHttpCredentials("John", "p4ssw0rd")
Get("/secured") ~>
addCredentials(validCredentials) ~> // adds Authorization header
route ~> check {
responseAs[String] === "The user is 'John'"
}
val invalidCredentials = BasicHttpCredentials("Peter", "pan")
Get("/secured") ~>
addCredentials(invalidCredentials) ~> // adds Authorization header
route ~> check {
status === StatusCodes.Unauthorized
responseAs[String] === "The supplied authentication is invalid"
header[HttpHeaders.`WWW-Authenticate`].get.challenges.head === HttpChallenge("Basic", "secure site")
}
From LDAP
(todo)