Although it is probably pretty clear that an (API) Gateway is meant to be between the caller of services (resource servers) and a client, there are still many subtle different deployment scenarios.
The most noticeable differences being mostly related to whether or not the client is a browser-based app and if-so whether or not the app, gateway and/or identify provider (IdP) share the same domain.
To prevent even more complexity the main focus of this exploration of a Gateway is for a browser-based client served from the same domain. This basically means the app itself is served through the gateway. With the IdP on another domain, hiding any login related traffic between the IdP and the browser from the gateway. Most parts of this exploration should be usable in other scenarios (native-clients) as well though.
Feel free to skip this background chapter
The usage of OAuth 2.0 is getting way bigger than acquiring third-party (social) authorization and is frequently used for all sorts of in-organisation AuthN/AuthZ scenarios as well.
To be clear, I’m talking about the Implicit
and Authorization code
flows here. In an attempt to fully move away from the now deprecated Implicit
flow, OAuth is now promoting the usage of the Authorization code
even for apps without a backend as stated in this new draft:
the current best practice for browser-based applications is to use the OAuth 2.0 authorization code flow with PKCE.
Promoting PKCE
to somewhat mitigate the chance of an access_token
being stolen due to leaking of parameters.
The same spec also makes a few other interesting statements:
it is best to avoid letting the JavaScript code ever see the access token … to keep access tokens out of the browser.
If your JavaScript application has no backend, but still shares a domain with the resource server, then it may be best to avoid using OAuth entirely.
Another great initiative to leverage OAuth 2 for authentication is the openid connect specification, which standardizes
"a simple identity layer on top of the OAuth 2.0 protocol"
Apart from the core specification it also defines additional useful extensions. Session management to use an OpenID Connect provider to also manage (SSO) sessions. Leveraging cross-origin inter-iframe eventing to receive session updates. Which is not for the faint of heart and obviously out of scope for this initial Gateway exploration but might be useful for complex scenarios.
Furthermore the multiple-response-types and form_post response mode specs help mitigate the leaking of tokens, by moving them out of the query string.
The OAuth support in the Spring framework is currently receiving a major overhaul, with support for basic client
and resource-server
scenarios
already released in the spring-security-oauth-…
modules. Version 5.2.0
will contain some separation of logic providing more integration hooks for others to use.
For simple scenarios to me this sums up to still preferring the Authorization code
flow with the code exchange step server-side. Using good old session cookie-based AuthN to your browser app if you can.
Or if you architecture doesn’t support or requires it, doing the entire flow client-side (with the additional requirements) if you must.
With all this in mind, lets see what this actually means when introducing a Gateway and the desire to off-load AuthN/AuthZ as much as possible to an external product.
A solid product when it comes to configuring OAuth 2.0 (OpenID Connect) based AuthN/AuthZ with loads of integration options is Keycloak.
With multiple of our clients using it, and it being fully Open Source, this should provide the perfect fit for handling the login
and providing the actual access_token
.
Allowing anyone to do this complete exercise on their local machine.
Just like the most standard scenario for the OAuth Authorization code
flow, we could have the gateway serve as typical backend. Taking care of exchanging any authorization code received after login for the actual access and refresh tokens server-side and storing them in a session.
Effectively playing backend for any JavaScript app that it serves. Personally I consider this the easiest option for browser-based apps. Mainly due to simplicity this provides to you client apps. Cookies are less suited for apps though and CSRF protection in most scenarios.
A solution following this architecture using the latest and greatest reactive goodness of Spring WebFlux and Security can be found at stateful-gateway.
For now the example is not based on spring-cloud-gateway
as it would only complicate the solution in its current state (see other branch).
Another option would be to embrace doing the Authorization code
completely client-side and have your IdP and app conform to additional requirements to prevent leaking tokens. Basically providing a browser app with the access_token (stored inside the sessionStore) and calling the Gateway with
Authorization: Bearer <access_token>
This way almost anything can serve as the Gateway and transparently proxy the request to the proper service. The major drawback being that the access token usable throughout your service landscape is directly exposed to the client, likely leaving your internal network. While this is technically feasible it is not very secure.
Enter token-exchange. Leveraging Keycloak’s Tech Preview support for token exchange it should be possible to create a Gateway that dynamically exchanges incoming access tokens representing the user, for an access token intended for internal use, when proxying API calls. The gotcha being that only the Gateway should be authorized to do this exchange, using the same …/token
endpoint as used by the client app itself to acquire its access token.
Unfortunately Spring Security currently does not yet provide direct support for token_exhange. When the time is right, this repo will hopefully contain a stateless solution as well. Feel free to contribute your own ;)
If you want go the stateless route but prevent javascript in browser clients from being able to touch/read the access_token
and dealing with OAuth complexities in general, you could also have the Gateway do the original code exchange or use the form_post response mode pointing to the Gateway.
This would allow the Gateway to deliver the tokens as http-only cookies to the client app. Effectively automatically using them when sending requests to the Gateway.
Note
|
any cookie-only based solution requires CSRF protection. |