Authentication & Authorization in a Microservices Architecture (Part 3)
IT Tips & Insights: In part three of this three-part series, a Softensity Engineer delves into authorization in microservices.
By Gustavo Regal, Software Engineer
Enforce role-based authorization at the pod level using a sidecar approach plus a policy distribution approach; list-based authorization is performed by the services themselves, as it involves checking for relationships between business objects.
Authorization in a Microservices Architecture
When we saw the monolith in Part 1, the implementation of authorization was quite thorough. In that particular design, one just had to do some queries to the database (keep some in-memory cache when useful) and determine whether to allow or reject requests.
In Part 2 we saw a solution for designing authentication in a distributed architecture such as microservices. But we did not touch authorization.
Now let’s take a look into some of the forces involved when talking authorization:
- Each service knows its features best, so it seems like a great candidate for applying authorization to requests addressed to it.
- On the other hand, authorization feels like a concern that is present in every service and thus would better be centralized?
- Specially LBAC can only be implemented at the service level, as it involves accessing the service’s intimate data.
Authorization at the BFF
Thinking of centralization, one common pattern is to apply RBAC (role-based authorization) at the BFF. As it intercepts all user requests, it can decide which ones should be authorized and which ones shouldn’t. It is capable of doing that if it has access to the authorization rules/policies, whether they are kept internally at the BFF or externally (in an authorization service).
- The centralized permission management could be easily achieved, as every rule/assignment is persisted to one single database.
- It seems like the Single-Responsibility Principle is happy here, as only the BFF is concerned with authorization.
- Actually, the BFF can only apply RBAC, as LBAC involves checking data kept at the upstream services.
- The BFF (or the authorization service) needs to know about all the authorization rules for every upstream service it works with and, every time a rule changes in any service, both sides need to be updated (high coupling).
- Those upstream services do talk to each other without involving the BFF, so what if the red service requests something from the green service which should not be authorized? Again, Zero Trust Architecture is something to recall.
- Those upstream services might be exposed through more than one BFF. In such cases, authorization would have to be resolved again.
Authorization at the Microservice Level
So, let’s just reason about each service being in charge of enforcing all its own authorization rules. Starting with Role-Based Access Control (RBAC): every time services tried to evaluate authorization for a given request, they would need to know the user’s roles. One option would be for them to query the Users Service, or the Authorization Service (whoever has the roles assignment info), every time a request arrives:
… which doesn’t seem right. That green circle has every other service depending on it. A supermassive black hole that every request falls into. That is once again a SPOF and a performance and availability degrader.
In order for the services to avoid depending on a central element, they should be able to determine the user’s roles locally.
Customizing the JWT
Recall the JWT we saw before?
What if those JWTs carried information on the user’s roles as well? One could add claims to that payload, as follows:
All you have to do is customize the token generation process to query for more information on the user prior to actually writing the token’s payload. That kind of customization can be done in pretty much any framework. I could cite here IdentityServer (or Duende) if you are in .NET.
All in place, our solution would look like this:
Now we only need the authorization rules. Where are they?
Storing and Accessing the Authorization Rules/Policies
First of all, what is meant by “rules” or “policies” here? Just the kind of sentence we saw previously:
“Every user with the sales-person role is allowed to create sales orders.”
If the rules/policies are stored at the service, then centralized management gets more complex and it seems like repeated implementation throughout the services. If they are stored at a central service (an authorization service), then we are talking about very frequently consumed data that is stored far away. Seems like a dilemma, doesn’t it?
Sidecars Once Again
A sidecar could enforce RBAC for our microservices, just the way a sidecar validated authentication, as seen in Part 2.
Imagine a sidecar that somehow has access to the authorization rules/policies. The pod’s proxy (another sidecar) could first check with it if the request is authorized and only then forward it to the actual microservice:
- The microservices are spared from implementing RBAC.
- Authorization is enforced at the microservice level (actually, at the pod level) instead of at the edge (as in the BFF), complying with the Zero Trust Architecture and making that pod more independent.
- How to manage rules/policies in a centralized manner?
Policy management and distribution
What if the rules/policies were managed in a central service, but then distributed to every authorization sidecar?
Fortunately, again, there is an open-source policy engine called OPA (openpolicyagent.org), that does just that. Policies can be written in JSON or Rego format, then packed and distributed throughout the cluster. The agents (how they call the authorization sidecars) can also poll for updates at given intervals.
That way, the information that is very often needed at the pod is locally available, providing performance and availability. As for the proxy sidecar, Envoy can be configured to act as a proxy, validating JWTs for authentication and integrating with OPA to implement authorization in the pod.
Authentication and authorization become much more complex if you’re doing microservices. The good part is that there is a lot of information sharing nowadays and most problems already have good solutions and something ready to (learn and) use.
Regarding authentication/authorization, solutions nowadays often rely on standards/frameworks/tools like JWT, OAuth 2.0, OpenID Connect, Kubernetes, Istio, Envoy and OPA.
Hey, I’m Gustavo Regal. I live in Porto Alegre (Brazil) and I’ve been a software engineer and a few more things since 2007, during which time I have designed and developed lots of different kinds of systems for all sorts of industries.
I am currently working as a tech lead and architect at Softensity, building distributed solutions using Azure, C# and other software.
Thank you for taking the time to read this blog. I would love to get your feedback and contributions.