We are moving from a monolithic platform to a more Service Oriented Architecture. We are applying very basic DDD principles and splitting our domain across different bounded contexts. Each domain is distributed and exposes a service via a web API (REST).
Due to the nature of our business, we have services such as Bookings, Services, Customers, Products, etc.
We have also set up an Identity Server (based on Thinktecture Identity Server 3) whose main role is to:
- Centralize authentication (given credentials it issues tokens)
- Add claims in the tokens such as: the scopes of the client (as per client I mean the application doing the request), customer identifier (as per customer I mean the person using the application)
We also introduced the role of an API Gateway which centralizes external access to our services. API Gateway provides functionalities which do not require deep knowledge of the internal domains such as:
- Reverse proxy: routes incoming requests into the appropriate internal service
- Versioning: a version of the API Gateway maps to different versions of the internal services
- Authentication: client requests include the token issued by the Identity Server and the API Gateway validates the token (make sure user is who says she is)
- Throttling: limit number of requests per client
What concerns authorization, this is not managed in the API Gateway but in the internal services itself. We are currently doing 2 main types of authorizations:
- Authorization based on client scopes. Example: a client (external application consuming our APIs) requires the scope "bookings" to access the Bookings service API endpoints
- Authorization based on the customer. Example: only if a customer (physical person using the application) is a participant of a booking can access the endpoint GET /bookings from the Bookings service
To be able to handle authorization in the internal services, the API Gateway simply forwards the token (when routing the request to the internal service) which includes both information about the client (the application doing the request) and the customer as a claim (in those cases where a person is logged in the client application).
So far so good until we introduced inter-service communication (some services can communicate with other services to obtain some data).
How should we approach the authorization in inter-services communications?
To discuss the different options I will use the following sample scenario:
- We have an external application called ExternalApp that accesses our API (ExternalApp can be seen as the client) in order to build the booking flow
- ExternalApp needs access to the Bookings service, hence we grant the ExternalApp the scope "bookings"
- Internally (this is something completely transparent for the ExternalApp) the Bookings service acesses the Services service to obtain the default services of a booking such as the flights, the insurances or a car rental
When discussing this issue internally several options popped up, but we are not sure which option is best:
- When Bookings communicates with Services, it should simply forward the original token he received from the API Gateway (indicating that the client is the ExternalApp)
- Implications: We might need to grant scopes to the ExternalApp that should not have been granted. Example: ExternalApp might need to have both the scope "bookings" and "services" while only the "bookings" scope could have sufficed
- When Bookings communicates with Services, it forwards a token indicating the client now has become Bookings (instead of the ExternalApp) + it adds a claim indicating Bookings is impersonating the original client ExternalApp
- By also including the information that the original client is the ExternalApp the Services service could also do logic such as filtering out some services depending on the original caller (e.g. for internal apps we should return all fights, for external apps only some)
- Services should not communicate with each other (so we should not be even be facing this question)
Thanks in advance for your input.