Achieving a solid microservice authentication and authorization implementation can certainly be a challenge. In this article, we show you rules and considerations you should follow along this path.
Dividing large software architectures into smaller and independent elements makes them easier to understand, implement, maintain, deploy, and scale when needed. However, splitting software into smaller components does not come for free. There is a toll to be paid upon setup of the infrastructure and most importantly, a learning curve on how to address specific business needs as distributed concerns.
Well-defined boundaries of each microservice addressing these concerns is key when building them, and it is also necessary to plan on how these services will collaborate between each other. Failing to plan ahead could result in tightly coupled microservices. This would bring us back to an almost monolithic application, which we hope to leave behind. Additionally, it becomes more complex to maintain multiple components and deployments.
Two good examples of items that can be challenging to implement as microservices and potentially cause tight dependencies and overlaps among all other services are authentication and authorization.
Authentication
Let’s explore authentication and authorization, starting with the former, since we must securely authenticate users first to then focus on our microservice infrastructure.
User authentication is a challenge on its own and is a great candidate to be self-contained, either as a microservice hosted by us in our own infrastructure or as an outsourced authentication service.
A mature and tested user authentication solution is essential to securing the infrastructure. This solution is also likely to be the first victim in an attack, as it is on the first line of defense for our application.
Adherence to modern authentication standards and following best practices on managing user information, such as ensuring password complexity and rotation, usage of multifactor authentication, reliance on secure data storages, and the usage of mature hashing and encryption algorithms, among other things, is paramount and can not be left to chance.
Either choosing an on-prem authentication solution or an outsourced one can help as long as there are well-defined and proven standards to integrate with this service instance. A great example of such a standard is OpenID with the usage of JWT tokens. Do not take a protocol as a silver bullet on its own, because even great protocol standards can be vulnerable if they are not configured properly or misused.
Getting user’s authentication to work
Once we are confident that our authentication solution has been hardened, tested, and can take on a suitable load without compromising the entire infrastructure, we can move to our next authentication challenge — interact with our application services as an authenticated user.
In order to trust users and consider their service requests as valid, it is quite common to include a token issued as proof of their successful authentication in every request. This proof of authentication can be implemented in multiple ways, but a signed, and sometimes also encrypted, JWT token issued for a limited amount of time and for a particular user ID and scope is the most common and standard alternative.
“Trust but verify” would be the rule going forward with this authentication proof issued to the user. Our microservices should not take for granted any request made with something resembling a user token without verifying its authenticity and checking if it is still current. Tokens need to be constantly checked against a trusted authentication verification service, which is usually the same one that issued the token. At the bare minimum, validations should check tokens that haven’t been tampered with, have been issued by a known and trusted source, and are not expired or used for a purpose different than issued, which is also information we can include in our tokens.
Microservices authentication alternatives
Does each microservice need to know how to validate these tokens? Not necessarily. Offloading this task from our microservices is possible by moving them behind a gateway or a proxy service that handles token check logic as requests come through it.
Another alternative to gateways is moving token logic checks to an ambassador service, which is deployed alongside our services and works as a front door to each microservice, intercepting only its related service requests. These ambassador components can be implemented once and deployed multiple times alongside each microservice.
Both alternatives above have the advantage of being isolated components. We do not need to know anything about token validation; we only need to expect ready-to-consume metadata added into the request to identify this user in our system, since we would still need to know who is making this request for business purposes.
If none of these implementations fits our needs, skills, or budget, security can be implemented as a plugin embedded into each microservice. This implementation would now be tied to the technology stack used with our microservices, but it reduces the footprint needed and some latency in communication, as both gateway and ambassador components increase the demand on infrastructure and add layers to the communication.
Everything comes with a tradeoff, since using a plugin approach would also create a dependency on our services that would need to be maintained and deployed in addition to any other logic in our service. Plus, it would limit us on what technologies or frameworks we can use, as they must be compatible with this plugin first.
Authorization
Now that an authentication mechanism has been established to securely authenticate users and let them invoke further actions in our services, it is time to move forward to our next challenge — authorization.
Access control should be thought of as a problem with multiple layers, as business domains are usually more complex than roles and static user assignments.
Some of these layers are going to be specific to a business domain addressed within a given microservice, with a specific service implementation to validate it. Other layers may be solved by using roles which can easily be wired into our gateway, ambassador, or security plugin.
In some situations, we may even think about moving authorization checks, at least some of them, into a common service. This option may be useful as long as implementing this service does not cause tight dependencies we would regret at a later day. When facing shared validation rules on multiple microservices, issuing a token with a specific business purpose is an alternative to take into account. Note that generating this custom token would require of a service able to issue it after running all required validations for the given purpose.
Conclusions
In monolith solutions, authentication and authorization are usually carried out using a single framework and within the same process that started the request. Business rules are centralized and are easy to validate in any part of the code. When moving to a distributed environment, users and access information are no longer located in one place, thereby making it hard to validate and trust.
The usage of authentication tokens, which can be securely verified and tested in a distributed environment, along with a careful layered authorization design, could help us achieve a solid microservice authentication and authorization implementation.
Comments? Contact us for more information. We’ll quickly get back to you with the information you need.