Custom route rejection end up in 405 Method not allowed

scala

#1

Hello,

We have two custom directives responsible for authentication and authorization of users, when something is wrong the directives are rejecting the request with two custom rejections (UnauthorizedRejection and ForbiddenRejection). We do have a custom rejection handler which maps those two rejections to a StatusCode.Unauthorized and a StatusCodes.Forbidden accordingly.

In our tests, everything works perfectly, when we wrap a route with our directives and send an unauthenticated or unauthorized request we can properly assert that the status code is either Unauthorized or Forbidden.

When we wrap the routes in our main API with one of our directives, we end up having a StatusCodes.MethodNotAllowed in place of the expected Unauthorized or Forbidden status codes.

I think it’s related to how akka-http handles route evaluation using the ~ operator.

After having been rejected by a route the request will continue to flow through the routing structure and possibly find another route that can complete it. If there are more rejections all of them will be picked up and collected.
https://doc.akka.io/docs/akka-http/current/routing-dsl/rejections.html

My question is, is there a way to stop the request to flow through the routing structure after some specific rejection are encountered? Or to customize the default rejection handler to send the proper status codes when the rejection list contains either a ForbiddenRejection or an UnauthorizedRejection ?

Thanks !


(Johannes Rudolph) #2

Hi @imclem

Yes, the reason is that at concat / ~ when a rejection is returned from a branch, the next branch is tried. After trying all the branches you might have ended up with a list of rejections, one for each branch, and then some response needs to be created which may choose a seemingly arbitrary rejection from one of the branches.

As a mitigation, you can seal the route at any point in the routing structure to convert rejections into final responses. You can either use the handleRejections directive for that and pass your custom rejection handler, or use Route.seal which uses implicit RejectionHandler / ExceptionHandler for the same purpose.

Johannes


#3

HI @jrudolph,

Thanks for your anwser !

Are there any implications if I seal the routes ? I don’t like the idea of having to seal the routes by myself in the routing structure :frowning_face:

To be sure that I’ve properly understood, I’ve two options:

  • Route.seal + implicit def myRejectionHandler
  • Wrapping my directives or routes with handleRejections and my custom rejection handler

Is that right ?

Clem


(Johannes Rudolph) #4

Usually, there are 3 possible results from a route (Future[RouteResult]):

  • Success(Complete(response)) once a final response has been generated
  • Success(Rejection(...)) if the route rejected the request
  • Failure(ex) if the route execution failed

Sealing just means that the latter two possibilities are ruled out by converting them to a final response.

See https://doc.akka.io/docs/akka-http/current/routing-dsl/routes.html#sealing-a-route more information.

There would be a third alternative which would be not using rejections in the first place but just returning the final error response directly.


#5

Is it possible to do something else than reject, pass and provide in a Directive1[Something] ?