Ocelot
Ocelot copied to clipboard
Authentication/Authorization - Conceptual Questions
I'm struggling to comprehend why is it so damn hard to do proper authentication/authorization if you hide your downstream services behind a gateway. Please help me to understand, and advise!
So, just a quick background info. We are currently working on a modular platform (micro-frontend/microservices) where different teams can add modules freely to the same system. We only need Azure AD authentication therefore I wanted to avoid using Identity Server and having a separate issuer service implementation. I'm trying to keep it simple.
As far as I understand, the fastest possible solution is if the downstream services don't do shit, we configure the Azure AD auth on Ocelot level only. That's fine I guess, but with this way claims won't be extracted automatically by the downstream services (that's why, I guess, Ocelot has this obscure claims transformation feature in the first place). I couldn't find any simple solution on the Internet to make a downstream service to accept the JWT token coming from the gateway. It's hard to believe that no one ever needed that before me.
The other problem I have with this approach is that I don't want to configure the Gateway all the time when another team creates a new API endpoint. They should be able to use the [Authorize] attribute in their code to finetune their authentication/authorization requirements. But with this approach authentication won't work, unless you do many hacks/workarounds. (Downstream service URL in HTTP 302 redirections and such, you can't initiate signin at downstream level and expect from the upstream to be able to finish correctly)
So, isn't here a better solution for this? Don't tell me nobody suffers from this. The web is full of utterly terrible solutions, one is worse than the other, while I don't want to reinvent the wheel here, I'm sure there must be a simple solution for this. Or am I missing something important? Please help!
Thanks!
Not sure how Azure AD works, I assume it is using OAuth 2.0 / OpenID Connect with a bearer token since you are referring to claims transformation.
the fastest possible solution is if the downstream services don't do shit, we configure the Azure AD auth on Ocelot level only
That's a way to do it.
That's fine I guess, but with this way claims won't be extracted automatically by the downstream services (...) I couldn't find any simple solution on the Internet to make a downstream service to accept the JWT token coming from the gateway.
By default Ocelot is forwarding headers, so Authorization header should forward the access-token as-is to downstream apis. You could parse the token there (using standard Authentication middleware in a dotnet core api for instance). But that could lead to other issues:
- Outside world might not be reachable from those Apis to validate the token with Azure AD.
- Audience would probably be wrong on the token since it was issued for the user to reach the gateway, not for the gateway to reach all downstream apis. You would have to either disable token validation or at least tweak it (TokenValidationParameters).
- User token is leaking in downstream services. If someone gets it, one could impersonate the end-user to perform actions in its name on the gateway.
- Finally the gateway itself should be using some kind of authentication for reaching downstream apis. It could take multiple forms, but in our case we are also using Authorization header with an OpenID Connect token issued for the gateway (using a server-to-server flow) with scopes/audience being all downstream apis.
The solution we've opted for:
- a token user/gateway that is validated by the gateway and does not leak downstream.
- 1 middleware in the gateway to strip all signature from the user token and just grab the claims part + 1 delegating handler to send that in a custom header to downstream apis.
- a server-server token gateway/apis validated by apis (not carrying any user data).
- 1 middleware in downstream apis to parse the user claims + 1 delegating handler to forward it when jumping from api to api if needed.
Not sure this is a perfect solution but that works for us and it is reasonably simple.
(that's why, I guess, Ocelot has this obscure claims transformation feature in the first place).
It could also be a way to simplify/restrict public endpoints. For instance, say you have a downstream route Get /users/:id/profile
. You could prevent anyone from calling this endpoint for another user than current user by simply using claims transformation to inject the user id into the downstream route, just exposing publicly a more restrictive endpoint Get /me/profile
.
The other problem I have with this approach is that I don't want to configure the Gateway all the time when another team creates a new API endpoint. They should be able to use the [Authorize] attribute in their code to finetune their authentication/authorization requirements
I don't see a simple way around that. I mean the gateway is meant to control which routes can be accessed, in between an application and the apis, so each team will probably want to update the gateway at some point. Imagine an api exposed by 2 gateways (2 applications) but not all routes should be in the first gateway (it could be a public web app versus a back office for the second gateway, with more sensible actions).
In our case we don't use standard Identity Users in the apis since the token used for authentication is not a end user one, but we are still able to check user claims in the apis using our dedicated UserService.
Not a perfect answer, but I hope it still helps.