expert
expert

Reputation: 30145

How to throw Exception from custom directive?

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

Answers (2)

4lex1v
4lex1v

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

expert
expert

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

Related Questions