When developing and deploying actual micro services, you do not want your consumer to access the microservices directly for several (main) reasons:
- it introduces heavy coupling on the client to your internal representation, to a certain degree this is similar to a case where you would expose a database directly,
- it complicates integration on the consumer side, because they need to remember where each service lives, on which URL,
- it is difficult to perform global checks and filters, such as API throttling,…
You have encountered one of those problems, which will certainly lead to others in the future.
Microservices should be internal in general. They may talk to each other in a synchronous (request/response) manner, although asynchronous (through events) communication is preferred, which makes it easier to deploy microservices separately, and also leads to more proper microservice design regarding their separation. With publicly accessible microservices, if you need to expose them to the outside world, put an API Gateway in front of them.
API Gateway acts yet as another system, through which flow all requests. It’s usually pretty light weight and responsible for routing of requests to appropriate internal microservices. But it has one great added benefit. You can put necessary global operations in it (since it’s your entry point). This includes authentication (who a user is) and optionally authorisation (is the user permitted to).
Since the API gateway authenticates your user (or otherwise rejects the request as a failed auth) and then forwards the request further to an internal microservice, the microservices themselves do not need to bother with authentication anymore. It’s guaranteed to be performed before they’re called, and they can trust the id of the user in the request. For most calls the id is the only thing necessary (without any additional checks), therefore in such context the operations needs not to bother with user logic at all.
With authorisation, it’s a little bit different and depends on the context. If there are simple rules such as: “This endpoint may be called only by users with the role admin,” then such rule can probably be placed in the API Gateway, to reject the request before even routing it into the microservice, which would reject it anyway. With more complicated authorisation closer to the business rules, it’s necessary that the given microservice keeps their copy of a user and checks against its database by itself (circumventing the synchronous like communication).
API Gateway provides also another benefit for your ecosystem. You can introduce multiple backends for your internal microservices, e.g. one for e-commerce, one for some mobile application,… thanks to which the consumer worries only about the exposed endpoints and is not overwhelmed by logic which needs to be internally available in your system but is only consumed from different contexts (therefore exposed as a different backend).
Why is synchronous communication frowned upon? It’s an easy mechanism how to obtain data among microservices, but it leads to direct coupling, because of which you need to introduce patterns like Circuit breakers, and starts to introduce single points of failure in your system, where a single failing microservice (on which tons of other microservices depend) may collapse your entire system. Event-driven systems are more resilient to this type of failure.