Reputation: 3126
Is there any rule how http4s prioritize AuthedRoutes
over HttpRoutes
when both of them are combined with <+>
👀
Because I receive 401 for such a combination authRoute <+> route
, when calling:
GET /non-authed-route
The behavior changes, and I receive 200 for the same request, when I reorder the routes route <+> authRoute
.
It may be even more messy when you need to work with 5+ routes which you need to combine and some of them may contain regular and secured endpoints simultaneously.
Upvotes: 3
Views: 2356
Reputation: 761
So, though the question lacks some context information or code, I am going to assume that your problem is similar enough to a common one:
First, I assume your code has this approximate form, and is built with an AuthedRoutes.of
(defined here), and an AuthMiddleware
:
val moonAuthedRoutes: AuthedRoutes[F, Int] = AuthedRoutes.of {
case GET -> / sky / moon as T(42)
}
val marsRoutes: HttpRoutes[F] = HttpRoutes.of {
case GET -> / sky / mars
}
// assuming you have your authedMiddleware
val moonRoutes = authedMiddleware(moonAuthedRoutes)
val composedRoutes = moonRoutes <+> marsRoutes
Now, it helps if you understand the types of the aliases involved, and what they come down to once you unwrap Kleisli
and OptionT
:
type HttpRoutes[F, T]
= Kleisli[OptionT[F, ?], Request[F, T], Response[F]]
~~ Request[F] => F[Option[Response[F]]
type AuthedRoutes[F, T]
= Kleisli[OptionT[F, ?], AuthedRequest[F, T], Response[F]]
~~ (T, Request[F]) => F[Option[Response[F]]
In essence, a function that takes the HTTP request and some authentication context, and may give compute some response or none (inside a computation in F
). To transform this AuthedRoutes
into an HttpRoutes
, you use an authentication middleware: a wrapper on your AuthedRoutes
that takes the Request, tries to extract the T
from it, if it succeeds it wraps the request with that T
and passes it to the AuthedRoutes
, as done in this line of code. If the middleware can not authenticate the request (that is extract the T
), as of this line, it short-circuits and returns a 401 Unauthenticated response.
Now, the <+>
composition first applies to the request on the moonRoutes
. If the result of that is some response, that is what the composed routes returns. Only if the moonRoutes
routes (at the left of <+>
) return an F(None)
will the composition pass the request on to marsRoutes
. In particular, when you give it an unauthenticated request, the moonRoutes
returns some 401 Unauthenticated response, and that is what the composedRoutes
return, without ever passing the request on to marsRoutes
.
If you swap the order, though, and write composedRoutes = marsRoutes <+? moonRoutes
, then unauthenticated requests to the unauthenticated endpoints are answered in marsRoutes
; whereas authenticated requests are first tried in marsRoutes
, which fails (gives an F(None)
), and then passed on to moonRoutes
, which succeeds.
Upvotes: 9