smithy4s icon indicating copy to clipboard operation
smithy4s copied to clipboard

`AuthedRoutes` support (http4s)

Open kyri-petrou opened this issue 2 years ago • 11 comments

Hi there 👋 I just want to ask, are there plans to add support for authenticated routes / middleware for http4s, or is this not on the roadmap?

kyri-petrou avatar Mar 03 '22 23:03 kyri-petrou

My comment will be mostly useless as I don't have any information on this :) so waiting for @Baccata's input. Here are my thoughts though, based on what I do know about http4s/smithy:

If we exposed AuthedRoutes[F, User], you'd still have to pass a function like Request[F] => F[User] (simplified). So you'd need to know where the auth data is located in the request, and Smithy is supposed to help you avoid dealing with that.

I imagine we could support Smithy's authentication traits and give you some sort of convenient API so that you implement something closer to Authentication => F[User], and User is part of your service's methods' inputs. Just spitballing though, this is just one of the possible implementations.

If we get into this territory, clients should also be able to send auth metadata.

kubukoz avatar Mar 04 '22 02:03 kubukoz

We don't currently have plans to support auth out of the box, I'm afraid. However, all annotations that are part of smithy specifications are code-generated in something called "Hints", which is exposed in various abstractions like smithy4s.Service (the instances of which are also code generated).

So users should be able to retrieve the auth annotations themselves and use them in their own middleware.

It is no coincidence that the http4s methods return http4s' HttpRoutes, as it allows for using existing middleware at that level.

That being said, I wouldn't mind looking at PRs if people have ideas on how auth should be implemented

Baccata avatar Mar 04 '22 06:03 Baccata

Apologies for the late response, must have missed the notification.

So users should be able to retrieve the auth annotations themselves and use them in their own middleware.

Thanks a lot for the info, didn't know we could retrieve the annotations!

It is no coincidence that the http4s methods return http4s' HttpRoutes, as it allows for using existing middleware at that level

The only downside is that Http4s' AuthMiddleware wraps around an AuthedRoutes instead of the normal HttpRoutes. Although it's not a huge issue since there's probably some way that the HttpRoutes trait can be converted to AuthedRoutes.

Thanks again!

kyri-petrou avatar Mar 15 '22 02:03 kyri-petrou

Hello. Is there still interest in adding this? I was poking around a bit to see what it would take.

Going off of how http4s does things, we'd need to specify a special structure that contains authorization information (typically called User). This could be marked with a new special trait - nothing in the current https://awslabs.github.io/smithy/1.0/spec/core/auth-traits.html seems to fit. Then the user would provide a function Request => F[User] that extracts user information from the request that is then passed on to the service. The User would be added as an extra param to all operations

The currently provided authentication traits could be used to simplify the process. ie if the service was marked with @httpBasicAuth, we could do the work of extracting the username and password from the Request into a case class BasicAuth(user: String, password: String), and then the user would just have to specify a BasicAuth => F[User] function, removing the low-level parsing step

This probably needs to be thought out a lot more so it can be properly generalized to other frameworks and use cases, but just my 2¢

sbly avatar Mar 26 '22 00:03 sbly

Hey @sbly. The guiding design principle of smithy4s is to avoid the tweak of the generated code at all cost for protocol-specific purposes. The reason is that we're keeping smithy4s protocol agnostic, and doing so unlocks a lot of interesting things. If we start catering to specific usecases by means of special handling in the code generation, we'll easily get trapped in a position that will prevent the implementation of these interesting, very valuable things.

The User would be added as an extra param to all operations

This can be achieved reasonably easily via other means without impacting code generation, for instance via ReaderT/Kleisli (or the Ask typeclass in cats-mtl). I'll probably get around to writing a cookbook at some point, to examplify solutions to these usecases.

Apologies if that's not a very satisfying answer. What we are trying to achieve with smithy4s goes way past the "http/rest" usecase, and I have to be harsh/fair and ensure that we don't take on any code that would prevent the achievement of that goal.

On the brightside however, it is really, really easy to write code generators for smithy, and that's in part why we're really interested in it. If you want a specific UX that caters to their own choices, it is definitely achievable to write your own tooling, and make it http/rest specific :)

Baccata avatar Mar 26 '22 08:03 Baccata

Hi everyone, sorry to bring this dusty ol'book up again. I was wondering, is there a way to extract the value of the Authorization header in the generated code? I managed to do it with custom headers but not with Authorization. Any pointers to examples would be very highly appreciated 😅

kyri-petrou avatar Jun 01 '22 22:06 kyri-petrou

Sure : there's two ways of handling this, depending on whether you're okay for the Auth header to pollute the generated interfaces or not.

#### First approach

The first way is to disable the Danger diagnostic associated with the attempting to set a header to Authorization. You can disable it by adding this at the top of the file :

metadata suppressions = [
    {
        id: "HttpHeaderTrait",
        namespace: "my.namespace",
        reason: "I really want to use discouraged headers"
    }
]

This lets you do

structure MyOperationInput {
    @httpHeader("Authorization")
    auth: String
} 

The reason for these headers to be discouraged is that AWS thinks such headers should be handled by the frameworks, and they're not entirely wrong. But smithy4s doesn't support authorization just yet.

#### Second approach

The second approach is more involved, but cleaner and more flexible : it has to do with creating an http4s middleware that would extract the values of the headers you need. Essentially it means you need a function that does :

type AuthedIO[A] = Kleisli[IO, Auth, A]
def authMiddleware(app: HttpApp[AuthedIO]):  HttpApp[IO]

Then you code your service against AuthedIO instead of IO, get an HttpApp[AuthedIO] from smithy4s, and feed that to the http4s server builder of your choice.

Baccata avatar Jun 02 '22 08:06 Baccata

@Baccata Hi,

would (or how would) the second approach be possible with a tagless final encoding/making the effect type polymorphic? I tried for a while but ended up playing "type tetris" and losing unfortunately. Thanks!

wvandermerwe avatar Aug 18 '22 23:08 wvandermerwe

@scala-impala, we have written a little guide to describe an approach we recommend for writing this kind of middleware. The guide describes monomorphic code, but we have a note at the end that gives tips on how to make it polymorphic.

Baccata avatar Aug 19 '22 08:08 Baccata

@Baccata I've got us set up with a cats-mtl (implicit Ask[F, User]) tagless final example which took some type tetris and would be happy to share it as an example in the docs

Functionally works really, however if we have a mix of auth/no-auth routes I'd quite like the Swagger docs to specify the header as required, even if Smithy doesn't pass it as a parameter. Is there a way to do that?

alexcardell avatar Oct 26 '22 11:10 alexcardell

@alexcardell I think using auth-trait will result in the corresponding routes being marked as authenticated in the openapi output.

I say "I think" because we're re-using a lot of code provided by AWS and I'm not 100% sure.

Baccata avatar Oct 26 '22 12:10 Baccata

Closing this : endpoint-specific middlewares are now available and can be used to implement auth. See the documentation : https://disneystreaming.github.io/smithy4s/docs/guides/endpoint-middleware

Baccata avatar Feb 11 '23 15:02 Baccata