Reputation: 30145
All API calls of my service are HTTP POSTs with parameters passed in multi-part body. Currently my authentication looks like this
formField("token".as[String]) { token =>
authorize(isAuthorized(token)) {
.... my other routes here
}
}
But it looks too verbose. Ideally, I'd like to have something like:
myAuthorization {
.... my other routes here
}
So I write it like this:
def myAuthorization: Directive0 =
formField("token".as[String]).require(isAuthorized(_))
But how would you write myAuthorization
so that it throws AuthorizationFailedRejection
with no token
in a request?
Upvotes: 3
Views: 2040
Reputation: 21567
I like to write spray directives with extractors. Simplified example from one of my projects:
def loggedInUser: Directive[Option[User] :: HNil] = headerValue {
case Cookie(cookies) => cookies.find(_.name === usrCookie) ∘ extractUserFromCookie
case _ => None
} | reject(NoUserLoggedInRejection)
def authOpt(usrOpt: Option[User], usr: String): Directive0 =
authorize(usrOpt.filter(_.login ≠ usr).isEmpty)
And then in your routing file you can use extractors:
pathPrefix("path") {
loggedInUser { user =>
authOpt(user, Clients.client) {
path("path") {
get {
complete {
html.page() // i'm using Twirl as a fe engine
}
}
}
}
}
If no user is logged in then it throws a rejection wich is handled seperatly:
implicit val customRejectionHandlers = RejectionHandler {
case NoUserLoggedInRejection :: _ =>
ctx => ctx.redirect("/auth", StatusCodes.SeeOther)
}
That not the best in case of security example, but i think it's clear enough
Added
Example with headerValue
directive from the comment:
// Lets define a extractor for the Host header from the request
val hostExtractor: HttpHeader => Option[String] = {
case Host(host, port) => Some(host)
case _ => None
}
// Then some path
val route = {
path("somePath") {
post {
headerValue(hostExtractor) { host =>
complete(host)
}
}
}
}
Most of such directives are implemented through extract
directive, which takes a function of type: RequestContext ⇒ T
and returns T
. For example headerValue
also implemented through extract
:
extract(_.request.headers.mapFind(protectedF)).flatMap {
case Some(Right(a)) ⇒ provide(a)
case Some(Left(rejection)) ⇒ reject(rejection)
case None ⇒ reject
}
Upvotes: 6
Reputation: 30145
Actually I realized that missing form field error might be misleading to the client. And here is how I return auth rejection if given token wasn't found in database
def myAuthorization = formField(FieldDefMagnet("token".as[String])).flatMap[User :: HNil]((token: String) => {
getUserByToken(token) match {
case Some(user) => provide(user)
case None => reject(AuthorizationFailedRejection)
}
})
I hope it helps other people.
Upvotes: 0