How to do authorisation?

We would like to implement authorisation

It would be nice to annotate the endpoints with for examples the roles. If the incoming request has that role (extracted from JWT) the user gets access.

I was thinking to use attribute and zServerSecurityLogic for this.

Question is, is this the right way of doing this? And if so, how do I get access to the attributes inside zServerSecurityLogic ??

I also would like to have some sort of abstraction around the partial endpoints similar to authentication.

I had tried using the .tags() on the endpoint to take in a list of roles but didn’t realize that that was for the OpenApi docs. So if you want, you can do that if generating an OpenAPI doc isn’t important.

Either way in the serverlogic I had to make a call to an authorization service I wrote that would return a failure if the authorization failed. But for that I had to pass in the ServerEndpoint val I had created for the method so i could get access to the tags and the endpoint path. This isn’t too scalable as it’s easy for a dev to make mistakes.

I would like a way with the authentication to chain from that partial endpoint into another partial endpoint that could generically handle multiple paths and multiple sets of permissions.

Hm that’s interesting. By design (so far), attributes on endpoint descriptions are to be used by interpreters. They are not accessible by the server logic. And as @frozenwizard noted, tags are for grouping the endpoints in documentation.

But maybe you can share a bit more about your use-case. So you perform authentication, parse the JWT token in the security logic? And then you have the roles? Or where are the roles available? Are they known statically, or dynamically - different for each request?

You can provide a constant as a “fake” input using .in(extractFromRequest(_ => List("a", "b"))). This would add the List[String] as an input parameter for the server logic.

So for your use-case, what you’d need is a way to access the server endpoint itself in the server logic? From there, you could extract the path and the like?

For my use case, I parse a JWT token and extract the roles and permissions from it. Every call could have a different set of roles and they’re statically known.

That would be a bit better, but I would want to move away from that. What I have now is

  val securedEndpoint: PartialServerEndpoint[JWTToke, UserContext, Unit, AuthError, Unit, Any, IO] = endpoint.securityIn(mapheaderstojwttoken)
    .errorOut(SomeError)
    .serverSecurityLogic(AuthenticationService.authenticate)

  val createModel: ServerEndpoint[Any, IO] = securedEndpoint.post
    .in("foo"/ path[Int]("resourceId") / "model")
    .in(jsonBody[Model])
    .out(stringBody)
    .serverLogicSuccess(userContext => (resourceId, model) => {
       authorizationService.authorizeResource(createModel, resourceId, userContext, List[String[("createPermission", "SuperAdmin")
       #actual server logic here
     )

I have a config file that checks the path to the allowed permissions in authorizeResource. I don’t like this as i have to maintain 2 places that does authorization, the config file and the endpoints. This can lead to devs making permissions mistakes with copy pasting the wrong stuff.

What I would like is a way to have some sort of metadata on constructing the endpoint would take in a list of permissions and then an authorization method. For many endpoints such as a GET, we only check for the permission, but for a POST/PUT/DELETE we check if the user has access to the resource.

  val createModel: ServerEndpoint[Any, IO] = securedEndpoint.post
    .in("foo"/ path[Int]("resourceId") / "model")
    .in(jsonBody[Model])
    .out(stringBody)
    .permissions(List[String]("create", "SuperAdmin")
    .authorize(customDefinedAuthorizationMethod(resourceId, userContext)
    .serverLogicSuccess(userContext => (resourceId, model) => #actual server logic here))
1 Like

You can attach arbitrary meta-data to an endpoint using attributes. So if you add an extension methods doing that (as is already the case for some tapir functionalities that are not in core), you could have a .permissions(...) syntax.

What is a bit unclear to me, is what would .authorize do exactly? Does it perform any logic different from what’s in the securedEndpoint?