This is a guest post written by Serge Bakharev. Serge is a software architect and a well-respected member / meme-er at AuthTown. In this post, Serge covers best practices to implement authentication and authorization in a microservice architecture. Take it away, Serge!
Microservices are Different
Identity and Access Management (IAM) is a crucial aspect of microservice architectures, as by definition they involve multiple independent services that need to authenticate and authorize requests from clients. This can pose a challenge as the independent services within the microservice-based platform need to be consistent and reliable in their application of IAM. If one service is broken this can be difficult to identify, and could become an entrypoint in the wider system for malicious actors.
Implementing IAM within microservices is challenging for a number of reasons, beginning with violating the Single Responsibility Principle of microservices. Microservices should only have a small set of responsibilities, however implementing IAM within each service adds additional functionality, and makes the services less reliable and more difficult to manage.
IAM within a microservice architecture can also be quite complicated. Consider that a single microservice could be accessed by users, other microservices, and third-party systems. All of which might have different rate limits, circuit breakers, and auth protocol requirements.
It can be tempting to simply abstract away these complexities to a library or framework which becomes a central dependency to all microservice codebases. But this ties you to a specific programming language, and goes against the core microservice architecture philosophy; teams developing microservices should be autonomous and independent of each other.
Getting IAM right in a microservice architecture can be more difficult than in a monolith.
Different technical approaches exist to solve these problems with varying compromises but as with many security controls and indeed cloud scale reliability in general, it’s best to have a multilayered or redundancy-included approach. Remote control plane enthusiasts call this “flying two mistakes high”; meaning to operate in such a way that two independent mistakes have to occur before a devastating outcome arises. Organizations with mature Reliability Engineering might even attempt to quantify the chance of these mistakes, and make an intentional decision to “fly more than two mistakes high”. This is especially true of IAM where a single mistake could make the difference between secure access and a security breach.
It is the position of NIST 800-204 (MS-SS-2) and other international standards that microservice architectures should enforce IAM access policies at least twice. First at the Edge via an “Access Server” such as an API gateway or Reverse Proxy through which all API traffic must flow, and a second time closer to the microservice, either at micro-gateway or the microservice itself. This way failure needs to occur in both levels before unauthorized access occurs. When implementing such a strategy it is normal to have the Access Server level perform coarse grain control such as grouping of API access by role and more detailed finegrain access such as object level ACLs implemented closer to the microservice.
For two independent IAM implementations to work in unison without dependency on each other, it is generally accepted that a stateless token approach is required. This way validation can be performed throughout the infrastructure without separately needing access to the associated session state information, which can be complicated to keep synchronized between systems. Stateless tokens also allow for clean decoupling of authentication from authorization. This separates the concerns of Identity proofing (Authentication) and access policy enforcement (Authorization). The token returned from the authentication system can be validated and enforced independently throughout the platform. Typically the technology of choice for stateless tokens is JSON Web Tokens (JWT).
Putting it Together
Implementing access policies twice might seem difficult but is actually quite easy when using an external authentication provider such as Descope and an industry standard claim interchange scheme such as JWT. As the authentication portion of IAM is handled by the provider, only the token validity and authorization checking needs to be performed in the microservice platform.
Modern API or Reverse Proxy gateways have token filtering capability either built-in or available as a plugin. Thus, implementation is as simple as configuring the gateway to filter access to only sessions with a valid token. The Access Server can validate the token via the public key of the external authorization system. To apply role based access control the API endpoints can be grouped based on role scoping, which can be an attribute included in the JWT token, and thus the coarse side IAM filtering can be performed with minimal Access Server configuration.
The fine grained access policies in the microservice could be implemented either natively in the microservice code, with calls to a dedicated authorization policy microservice, or via a micro-gateway sidecar when applying the Ambassador pattern.
Below is an example with a typical request/response application architecture accessing an admin scoped API endpoint. The flow is as follows:
The user completes the authentication via the Authentication Service, which returns a JWT for the web client to use with their subsequent API requests.
The web client checks that the JWT has not expired (refreshing if required), and if valid sends the HTTP request containing the JWT to the application platform’s API gateway.
The API gateway validates that:
The token is valid via the JWT validation plugin.
The API endpoint requested is scoped for use by the role granted to the user in the JWT. If the request passes JWT validation it is passed on to the backend microservice associated with the API endpoint.
The microservice receives the request via its own microgateway which also performs the token validity and access validation.
The microservice might need to communicate with other microservices. It is important that all services only allow authenticated communication, even between each other, as otherwise there may be a risk of privilege escalation or lateral movement via a compromised host within the application platform.
NIST Security Strategies for Microservices-based Application Systems - https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-204.pdf
Ambassador pattern - https://learn.microsoft.com/en-us/azure/architecture/patterns/ambassador
Access Token pattern in Microservices - https://microservices.io/patterns/security/access-token.html
Single Responsibility Principle - https://blog.cleancoder.com/uncle-bob/2014/05/08/SingleReponsibilityPrinciple.html