A few weeks ago I was looking into authentication and authorization architecture patterns for microservice ecosystems and found this lonely, unanswered stack overflow question on the topic.
This piece is a long overdue discussion of the questions they raised.
If video is more your style, check out a lightning talk I recorded on YouTube.
Executive Summary
To manage authentication in a microservice context you probably want:
- A User Service to store user data including permissions
- An
/authenticate
endpoint on the service for authenticating
Managing authorization is tricker. Here are two paths:
- Create an
/authorize
endpoint in the User Service that other APIs can call to allow/deny access- Best option if your microservice ecosystem needs to support an arbitrary number of languages/frameworks and you have engineering resources to build a service & endpoint registry
- Send blob of user payload data in
/authenticate
response and allow downstream services to parse and pass to business logic.- Best option if all your APIs are in a single language/framework and can share business logic code
Terms
Authentication means identifying who is making a request against one of our APIs.
Authorization means determining what a user can do in our system once they have been identified.
Guiding Principles
Microservice Single Responsibility
A microservice should operate in a single domain. This means a microservice generally should not be responsible for two things. For instance, not user management and reporting.
Single Source of Truth
For any piece of data in our system there should be a single source of truth.
Implementation Cost
Django as a monolith has an entire framework for authentication and authorization. Can these freely available tools be leveraged in a microservice context?
Handling Authentication
Regardless of how we decide to handle authorization, we will want a single place to manage our users.
This implies that at the very least we should have a separate service responsible for user management and authentication.
Let’s call this endpoint /authenticate
.
While processing a request in downstream APIs, we can forward a bearer token to this endpoint to determine what user is making a request to any given microservice.
If we have an API gateway, this is is a great place to do this.
Easy enough.
Handling Authorization
Determining whether a user is authorized to do something or not in our system is trickier.
Here we have at least two paths:
- Put permission business logic in external service
- Keep permission business logic local to each service
Breaking Out Permissioning Business Logic Into an /authorize Endpoint

Reasoning purely from our principle of single responsibility, we might think about a /authorize
endpoint in our User microservice.
On each request, after authenticating with the /authenticate
endpoint, we could make another call to /authorize
to determine whether or not the user is authorized to perform the given action they are trying to.
Obviously we could probably combine the /authenticate
and /authorize
endpoints to speed things up, but for the sake of conceptual clarity let’s keep them separate for the time being.
This involves more or less creating a registry of services and endpoints within our microservice architecture and then linking these to permissions or groups of permissions.
When a request comes in to a downstream API, it can check with the /authorize
endpoint to approve or deny a user’s request.
Analysis of the /authorize
endpoint approach
Advantages
This is a great option if your microservice ecosystem contains codebases in many different languages or formats. It abstracts the business logic of allowing and denying requests into a single entity
This aligns well with our Single Responsibility Principle as well.
Disadvantages
The biggest disadvantage to building this kind of system is that it requires engineering lift to build out a registry of services and endpoints and a mechanism to tie them into your permission system.
While by no means insurmountable, it may not be the best way of allocating engineering time for the business.
Additionally, keeping tests working and up to date in both the user service and downstream APIs becomes more complicated when permissioning is abstracted into a separate service.
In particular, how do you know that the configurations in your user service match the real world deployments of your downstream APIs? How can you ensure that a new release will not accidentally cut users off from endpoints they are authorized to access?
These are all questions worth thinking about.
Delegate Authorization To Downstream APIs

Another approach worth mentioning is delegating permissioning business logic to downstream APIs.
As I mentioned earlier, many frameworks such as Django come with an entire system for managing authentication and authorization in a monolith context.
If we want to skip building out the business logic of an /authorize
endpoint, one alternative is to have the user management service send back a list of user permissions in its /authenticate
response.
The downstream API can then read the user’s permissions and plug them into its own business logic for permissioning.
Analysis of Delegated Authorization Business Logic
Advantages
One of the big advantages of a solution like this is that downstream APIs using frameworks like Django can simply plug the permission payload into their existing system.
If you are managing a limited number of APIs, and especially if those APIs are written in the same language and framework, this can be a really easy, low-lift way of managing permissioning across an API ecosystem.
Disadvantages
As stated before, if you are working in a multi-language, multi-framework environment you may not want to keep re-writing systems for interpreting the permission blob in each language or framework.