Authentication and authorization in a microservice architecture: Part 1 - Introduction

application architecture   architecting   security  

Public workshops:

  • Enabling DevOps and Team Topologies Through Architecture: Architecting for Fast Flow JNation, May - Coimbra, Portugal Learn more
  • Designing microservices: responsibilities, APIs and collaborations DDD EU, June 2-3, Antwerp, Belgium Learn more

Contact me for information about consulting and training at your company.


This article is the first in a series of articles about authentication and authorization in a microservice architecture. The complete series is:

  1. Overview of authentication and authorization in a microservice architecture

  2. Implementing authentication

  3. Implementing simple authorization

  4. Developing complex authorization

  5. Implementing complex authorization using Oso Cloud - part 1

  6. Implementing complex authorization using Oso Cloud - part 2

Authentication is the mechanism for verifying a user’s identity while authorization controls what the user can do. They are, of course, part of the much broader field of application security. Security is a difficult topic in general, and the distributed nature of the microservice architecture presents some unique challenges.

This series explores how to implement authentication and authorization in a microservice architecture. Part 2 of the series covers authentication, while the later articles delve into authorization. In particular, I will describe how to implement authorization in a microservice architecture when the necessary data is scattered across multiple services. I’ll compare and contrast implementing complex authorization requirements within the individual microservices versus using a dedicated authorization service, such as Oso Cloud.

In this article, I cover some foundational concepts. First, I provide an overview of a simple application that’s used throughout this series. Next, I will look at authentication and authorization in a monolithic architecture. While this series focuses on microservices, starting with the monolith provides a solid introduction to authentication and authorization. It allows us to contrast the simpler, centralized approach of a monolithic architecture with the complexities and distributed nature of a microservice architecture. Finally, I will discuss some of the key challenges of implementing authorization in a microservice architecture.

About the example application

RealGuard.io is a fictional commercial real estate security system platform. The platform safeguards properties such as offices, stores, and warehouses. It manages security systems and provides comprehensive monitoring and protection for physical spaces.

alarm system context

The platform’s users are employees of three different types of companies:

  • Security system dealers - employees of companies that sell, install and manage security systems

  • Customers - employees of companies that have security systems installed at one or more of their properties

  • Monitoring providers - employees of companies that monitor security systems on behalf of dealers

The primary function of the platform is to enable users to perform various actions on security systems:

  • View security systems

  • Arm and disarm systems

  • Manage the notification list for each security system

  • Cancel alarms that have been triggered

Of course, not every user can perform every action on every security system. Alarm dealers can only access the security systems belonging to their customers. Monitoring providers can only access the security systems of the customers served by the dealers they work with. Customers can only access the security systems at their own properties. What’s more, their access might even be restricted to specific times, such as the start or end of their shift.

Each company that uses the platform has administrators who define what actions their employees can perform on which systems. The administrators can also define teams of employees that can manage the security systems at one or more locations. In addition, security system dealers can register customers and their locations and configure security systems. Monitoring providers respond to alarms triggered by security systems by contacting either the police or the customer. They can also cancel alarms, if necessary.

Let’s now look at how authentication and authorization work in the RealGuard.io application. First, I explore how they work in a monolithic architecture. Then, I discuss the challenges of implementing authentication and authorization in a microservice architecture.

Authentication and authorization in a monolithic architecture

The monolithic architecture is an architectural style that structures the application as a single component, which is an executable or deployable unit, such as a Java WAR file or executable JAR file. It’s a perfectly good choice for many applications, although some applications might outgrow it and require the microservice architecture. The following diagram shows the high-level architecture of the monolithic version of the example application:

monolith security big picture simplified

The architecture consists of the following elements:

  • A React-based UI

  • A monolithic application

  • A database containing business entities including companies, dealers, locations and security systems

Let’s look at how the application handles authentication and authorization.

Authentication in a monolithic architecture

Like most applications, a user logs in with a username and password. Once the user has successfully logged in, the application issues a session token cookie to the UI. Each later HTTP request from the UI to the application’s REST API includes this session token cookie. This session token is used to identify the user and, in some cases, their roles. In particular, it provides the information needed to perform authorization checks.

Authorization in a monolithic architecture

Since the application’s users are outside the firewall, its endpoints are publicly accessible. It’s vital, therefore, that the application implements proper authorization checks. The nature of those authorization checks will depend on the endpoint being accessed:

  • Public endpoints - no authentication or authorization required

  • Authenticated-only endpoints - user must be logged in

  • Authorized endpoints - not only must the user be logged in but their identity, their roles and their relationship with the resource being accessed determine whether they are permitted to perform the operation

An authorization check can be represented by the isAllowed() function, which determines whether the user is allowed to perform the operation on the resource.

isAllowed(user, operation, resource)

For example, in the RealGuard.io application only specific users can perform certain operations, such as disarming a property’s security system. At a high level, the application enforces authorization by evaluating whether a user is allowed to perform a specific operation on a given resource. This can be expressed as a simple function call:

isAllowed(user, "disarm", SecuritySystem(ID))

In this example:

  • user - represents the authenticated identity making the request

  • disarm - the operation the user is trying to perform

  • SecuritySystem(ID) - the resource being accessed

Let’s now look at how isAllowed() can decide whether to grant access.

Flavors of authorization

There are four authorization models:

  • Role-based access control (RBAC)

  • Relationship-based access control (ReBAC)

  • Attribute-Based Access Control (ABAC)

  • Combining RBAC, ReBAC and ABAC

Let’s look at each of these in turn.

Role-based access control (RBAC)

The first model is Role-based access control (RBAC). A user is assigned one or more roles. Each role is a collection of permissions, each of which grants the user the ability to perform an operation on one or more resources. The isAllowed() operation verifies that the user has a role that grants them permission.

In some applications, a user’s roles are retrieved at login and stored in the session or the session token. In other applications, the user’s roles are stored in the database and retrieved by the authorization logic when needed. Some applications use a combination of both approaches. Later in this series, I’ll discuss these two approaches in more detail.

Relationship-based access control (ReBAC)

Another authorization model is relationship-based access control (ReBAC). When using ReBAC, access decisions are based on the user’s relationship with the data they are trying to access. For example, in a typical consumer-oriented e-commerce application, such as Amazon.com, a customer can view and cancel their own orders. In other words, the isAllowed() operation determines whether to grant access based on the relationship between the order and the user.

Attribute-based access control (ABAC)

RBAC and ReBAC are subsets of a more general authorization model, attribute-based access control (ABAC). ABAC uses the attributes of the user, the resource and the environment to determine whether to grant access. For example, the visibility (public vs. private) of Github repository is an example of an attribute that’s used for authorization. If the repository is public, then anyone, even anonymous users, can view it, whereas if it’s private, access is determined by roles and relationships.

Combining RBAC, ReBAC and ABAC

Quite often, RBAC, ReBAC and ABAC are used together. The user’s relationship with the data determines the roles that grant them permissions. For example, as I describe below, a user’s role within a company can grant them access to the security systems at all or some of the company’s locations. To check permissions, isAllowed() traverses the relationships between security systems, locations, and companies to determine the user’s role and, hence, permissions. Moreover, for some users, their work schedule (i.e., their attributes) and the time of day (i.e., their environment) also determines whether they can perform an operation.

Authorization examples

Let’s now look at how the user can be granted access to a resource in the RealGuard.io application. For operations that act upon security systems, isAllowed() grants access if one of the following is true:

  • The user is assigned the X role in one of the following:

    • The security system’s location

    • The security system’s location’s company

    • The security system’s location’s dealer

    • The security system’s location’s dealer’s monitoring provider

  • The user belongs to a team that is assigned the X role in the security system’s location

As you can see, a combination of ReBAC and RBAC determines whether a user is allowed to access a security system.

The following table shows the operation and the required role.

Operation Required role
  • getAlarmSystem()

  • getAlarmSystems()

SecuritySystemViewer

  • armSystem()

SecuritySystemArmer

  • disarmSystem()

SecuritySystemDisarmer

  • cancelSystem()

SecuritySystemAlarmCanceller

In addition to the required role, some operations have other constraints. First, some employees can only disarmSystem() within a specific time window, such as the start of their shift - a form of ABAC. Second, an employee that doesn’t have the SecuritySystemAlarmCanceller role can still cancel the alarm if they are on the location’s notification list.

Where to enforce authorization

Now that we’ve looked at some examples of authorization rules, let’s look at where authorization enforcement can occur. The monolithic application has a hexagonal architecture and so consists of inbound adapters, which handle inbound requests; outbound adapters, which make requests to the outside; and domain logic, which implements the business rules.

The following diagram shows the architectural elements that implement a request:

monolithic authorization enforcement http flow

Potentially, authorization can be enforced at the following places:

  • Edge network infrastructure - network elements in between the browser and the application

  • REST API - an inbound adapter consisting of HTTP request handlers, such as Spring MVC or Rails controllers or Express routes, that invoke the business logic

  • Domain logic - the implementation of the business rules

  • Security System DB interface - an outbound adapter, such as a Data Access Object (DAO), which is invoked by the domain logic and implements data access logic for the security systems tables

Also, while the UI cannot be relied upon to implement authorization, the user experience can be tailored to reflect the user’s permissions.

Let’s now look at how authorization can be enforced in each of these locations.

Network infrastructure

The network infrastructure can potentially enforce some authorization rules. There are, for example, technologies in the Kubernetes ecosystem, such as ingress controllers and service meshes, that can implement authorization checks for requests entering the cluster. A potential benefit of enforcing authorization rules at this point in the flow is that it can happen automatically, without requiring additional work by the developer.

The challenge, however, is that the network infrastructure has limited information on which to base authorization decisions. It does not have access to application data, such as the user’s relationships with the resources they are trying to access. The network infrastructure only has access to the request itself: the HTTP method, path, and headers, which include the session token.

REST API

The next element in the request flow is the application’s REST API adapter, which consists of HTTP request handlers. HTTP request handlers typically do not have access to the database, as doing so would duplicate business and data access logic. However, unlike the network infrastructure, they are guaranteed to have access to the user’s session, which contains their identity and potentially their roles. As a result, the HTTP handlers can implement request-level authorization equivalent to:

isAllowed(user(identity, roles, ...), method, path)

What’s more, as you will see later in this series, some web frameworks require just a few lines of configuration to enforce rules that require authentication and specific roles for all endpoints, except for those explicitly marked as public. This makes the inbound adapter a convenient place for enforcing coarse-grained, request-level authorization before the request reaches the business logic.

Domain logic

Once an HTTP request handler has authorized the request, it invokes the business logic. The business logic has access to the application data and the user’s identity and roles from the session or request. It can, therefore, implement much more sophisticated authorization checks than the inbound adapter.

Consider, for example, the isAllowed() check that’s invoked by the disarmSystem() operation. The isAllowed() check can easily query the database to verify that one of the following is true:

  • The user is assigned the SecuritySystemDisarmer role in one of the following:

    • security system’s location

    • security system’s location’s company

    • security system’s location’s dealer

    • security system’s location’s dealer’s monitoring provider

  • The user belongs to a team that is assigned the SecuritySystemDisarmer role in security system’s location

Furthermore, if necessary, isAllowed() can easily verify that the current time corresponds to the user’s shift.

Database access logic

The fourth and final location to enforce authorization is within the data access logic such as the Security System DB interface. It is generally preferable to centralize authorization in the business logic layer to ensure that database access logic remains focused solely on data access. There are, however, two key reasons to integrate authorization checks directly into the SQL statements executed by the database access logic.

First, it ensures that users can only retrieve data they are authorized to access. Embedding authorization into the query itself reduces the risk of accidental data exposure by enforcing constraints at the database level. Some legacy applications even enforce authorization checks using database views.

Second, integrating authorization checks into bulk queries often improves their performance. By filtering data in the database, rather than in the business logic, the system can take advantage of database optimizations to handle large datasets securely and efficiently. Consider the findSecuritySystems() operation that returns the list of security systems that the user is authorized to view. One way to implement this operation is to first query the database and then invoke isAllowed() for each security system returned by the query. This approach may offer acceptable performance when dealing with a small number of security systems. However, as the volume of data increases, this approach becomes inefficient.

When processing large amounts of data, a more efficient approach is to implement the authorization check for a bulk query in the database query executed by the database access logic. The SecuritySystemDao can, for instance, implement a findSecuritySystems() operation that executes a query that joins the security system tables with the other tables that define the user’s permissions. This query returns only those security systems that match the search criteria, and the user is authorized to view.

Tailoring the user interface

While the UI cannot enforce authorization, tailoring the interface based on a user’s permissions is essential to provide a good user experience. By dynamically enabling, disabling, showing, or hiding UI elements according to the user’s permissions, the UI will clearly reflect the user’s permissions. Also, when an action is not permitted, it’s often helpful if the UI explains why. To support this functionality, the application must provide the UI with the user’s permissions. For example, the GET /securitySystems/{ID} endpoint should return not only the security system’s details but also the user’s permissions for that security system.

Authentication and authorization in a microservice architecture

Let’s now look at authentication and authorization in a microservice architecture, which is an architectural style that structures the application as a collection of two or more services. The following diagram shows the high-level architecture of the microservice architecture version of the example application.

msa security big picture simplified

The architecture consists of the following elements:

  • React-based UI - same as before

  • Backend for front-end (BFF) - a dedicated API Gateway for the UI that handles login and forwards requests from the UI to the services

  • Customer Service - manages customers, employees, employees’s roles and the customer’s locations

  • Customer Service Database - stores customer information and is private to the Customer Service

  • Security System Service - manages security systems

  • Security System Database - stores security system information and is private to the Security Service

  • Message broker - used for inter-service communication including the service collaboration patterns that rely on asynchronous messaging

  • User Service - manages users, credentials and their roles

  • User Service Database - stores user information and is private to the User Service

Let’s look at how authentication and authorization work in this architecture, starting with authentication.

Authentication in a microservice architecture

Just like in the monolithic application, a user logs in with a username and password. However, in a microservice architecture, the Backend for Frontend (BFF) - rather than the services - participates in the login process. The BFF maintains the user session, which includes the user’s identity, and issues a session token to the UI. I’ll describe the details of how login is implemented in a later article.

Another important difference from the monolith is that the business logic that needs the user’s identity cannot simply access the user session via an in-memory mechanism, such as a ThreadLocal variable. In a microservice architecture, that business logic is implemented in services that run as separate processes. Consequently, as I describe below, the BFF, which knows the user’s identity (and perhaps their roles), must pass that information to these services.

Authorization in a microservice architecture

The following diagram shows the flow for a request that is ultimately handled by the Security Service Service. The request flows from the browser to the Backend for Frontend (BFF), which forwards it to the Security System Service.

msa authorization enforcement http flow

In some ways, the flow is similar to the one in the monolithic architecture. It simply has two additional elements: the BFF and the inter-service network infrastructure. The Security Service plays the role of the application. It even has a similar structure. However, implementing authorization logic can be challenging because services, such as the Security Service, only store a subset of the application’s data. To understand this challenge, let’s look at where authorization can be enforced.

As with the monolithic architecture, the UI can tailor the user experience based on the user’s permissions, and the edge network can enforce some authorization policies based on the contents of the request. However, compared to the monolithic architecture, there are a couple of new locations where authorization can be enforced: the Backend for front-end (BFF) and the inter-service network infrastructure.

Let’s look at implementing authorization in the following three locations:

  • Backend for front-end (BFF)

  • Inter-service network infrastructure

  • Within each service

Let’s start by looking at the BFF.

Backend for front-end (BFF)

In a microservice architecture, the BFF acts as a gateway to the backend services. Consequently, it is the first potential location within the application for enforcing authorization. In some ways, the BFF is similar to the inbound adapter in the monolithic architecture. It typically does not have access to the database, as doing so would duplicate business logic. However, it does have access to the user’s session, which contains their identity and, potentially, their roles. As a result, the BFF can implement request-level authorization equivalent to:

isAllowed(user(identity, roles, ...), method, path)

Once it has completed its authorization checks, the BFF forwards the request to the appropriate service, which in this example is the Security System Service. Because the services, and possibly the inter-service network infrastructure, need to know the user’s identity and roles, the BFF typically includes a token in the request, such as a JSON Web Token (JWT), that contains this information. I will describe this mechanism in more detail in a later article.

Inter-service network infrastructure

In a monolithic application, the application’s modules communicate using method or function calls. But in a microservice architecture, the services (including the BFF) communicate with one another over a network. Consequently, another potential location for authorization enforcement is the network infrastructure that handles inter-service communication. There are, for example, technologies in the Kubernetes ecosystem, such as service meshes, that can implement authorization checks for inter-service communication.

The network infrastructure that routes the request from the BFF to the Security System Service could, for example, implement some authorization checks on behalf of that service:

isAllowed(user(identity, roles, ...), method, (service, path))

It could extract the user’s identity and roles, if available, from the JWT included in the request by the BFF. This allows the network infrastructure to offload some authorization responsibilities from the services themselves, although it is limited to checks based on the data contained in the token.

Within each service

Once the request passes the previous authorization checks, it arrives at the target service. Each service has an architecture that is similar to that of the monolithic application. It consists of components such as the REST API inbound adapter, the domain logic, and the outbound database adapter, which collaborate to handle a request. As in the monolith, authorization enforcement can happen at each of these layers.

The challenge: data is scattered across services

But there’s one characteristic of the microservice architecture that can make it challenging for a service to implement authorization: the necessary data might be scattered across multiple services and accessible only via their APIs. For example, earlier when discussing how to implement authorization in the monolith, I described how the business logic that implemented disarmSystem() could simply query the database. Similarly, the SecuritySystemDao.getSecuritySystems() operation could execute a query joining the security system tables with other tables that define the user’s permissions. But, as the following diagram shows, implementing the equivalent checks in the Security System Service is not as straightforward since it needs data from the Customer Service and User Service.

msa security system permission

Not only is the Security System Service unable to execute queries that join with the necessary tables, but the overhead of inter-service communication can also be a significant concern.

An application might be able to enable a service, such as the Security Service, to implement authorization checks using only its own data by passing the necessary permission information in the request from the BFF. For example, when the user logs in, the application could store their roles and permissions in the session and pass them in the JWT. However, this approach has a number of drawbacks, including limits on the size of a JWT, as well as the performance overhead of sending a large JWT in every request and verifying its signature.

The problem of authorization checks spanning services is a special case of the more general problem of how to implement distributed queries in a microservice architecture. Normally, you can implement such queries using the service collaboration patterns. But, as I describe in a later article, there are additional considerations when implementing authorization.

Summary

  • Authentication is the mechanism for verifying a user’s identity, whereas authorization determines what actions a user is allowed to perform

  • An authorization check can be represented by the isAllowed(user, operation, resource) function, which determines whether a user is allowed to perform a given operation on a resource

  • There are three main authorization models, each of which determines access based on a different principle:

    • RBAC (Role-Based Access Control) – the roles assigned to the user

    • ReBAC (Relationship-Based Access Control) – the user’s relationship to the resource

    • ABAC (Attribute-Based Access Control) – attributes of the user, resource, and environment

  • RealGuard.IO - the fictitious, example application used throughout the series - uses a combination of the RBAC, ReBAC and ABAC authorization models

  • It’s straightforward to implement authorization in a monolithic application because the required data is in a single database

  • Implementing authorization in a microservice architecture can be challenging because the data needed for authorization is often scattered across multiple services

Acknowledgements

Thanks to Indu Alagarsamy, YongWook Kim, and Jong Il Kim for reviewing this article and providing valuable feedback.

What’s next

In the next article, I will look at how to implement authentication in a microservice architecture. After that, the articles that follow will explore different ways to implement authorization and solve the problem of distributed data.

Need help with accelerating software delivery?

I’m available to help your organization improve agility and competitiveness through better software architecture: training workshops, architecture reviews, etc.

Learn more about how I can help


application architecture   architecting   security  


Copyright © 2025 Chris Richardson • All rights reserved • Supported by Kong.

About Microservices.io

Microservices.io is brought to you by Chris Richardson. Experienced software architect, author of POJOs in Action, the creator of the original CloudFoundry.com, and the author of Microservices patterns.

ASK CHRIS

?

Got a question about microservices?

Fill in this form. If I can, I'll write a blog post that answers your question.

NEED HELP?

I help organizations improve agility and competitiveness through better software architecture.

Learn more about my consulting engagements, and training workshops.

LEARN about microservices

Chris offers numerous other resources for learning the microservice architecture.

Get the book: Microservices Patterns

Read Chris Richardson's book:

Example microservices applications

Want to see an example? Check out Chris Richardson's example applications. See code

Virtual bootcamp: Distributed data patterns in a microservice architecture

My virtual bootcamp, distributed data patterns in a microservice architecture, is now open for enrollment!

It covers the key distributed data management patterns including Saga, API Composition, and CQRS.

It consists of video lectures, code labs, and a weekly ask-me-anything video conference repeated in multiple timezones.

The regular price is $395/person but use coupon RPPPOGHM to sign up for $95 (valid until March 26th, 2025). There are deeper discounts for buying multiple seats.

Learn more

Learn how to create a service template and microservice chassis

Take a look at my Manning LiveProject that teaches you how to develop a service template and microservice chassis.

Signup for the newsletter


BUILD microservices

Ready to start using the microservice architecture?

Consulting services

Engage Chris to create a microservices adoption roadmap and help you define your microservice architecture,


The Eventuate platform

Use the Eventuate.io platform to tackle distributed data management challenges in your microservices architecture.

Eventuate is Chris's latest startup. It makes it easy to use the Saga pattern to manage transactions and the CQRS pattern to implement queries.


Join the microservices google group