Microservice architecture has now become the de facto choice for building applications. Other styles of architecture like a monolith, layered, SOA, or plugin are now considered less popular. This increase in uptake of microservice architecture depends a lot on its features that help businesses to become more agile and sustainable, and secure desired outcomes.
But businesses cannot prosper without considering the security of the infrastructure first, which puts entrepreneurs on dubious ground as security in microservices is a bigger challenge compared to monolith.
Why Is Microservice Architecture Vulnerable?
There are five major reasons that make microservices a risk for entrepreneurs.
- Broader Attack Surface – Monolith has fewer entry points as compared to microservices. Internal components communicate within a single process. For example, a Java-based monolith application communicates within the same Java Virtual Machine (JVM). But, in a microservice architecture, internal components are designed separately in the form of independent microservices and those in-process calls amongst internal components get changed to remote calls.
- Performance Impact – Security screening in microservices introduces inferior performance compared to the monolith. In a monolith, the security screening of a request happens only once whereas in microservices, it happens with every service, which may sometime involve connecting to a remote security token service.
- Bootstrapping Trust – Bootstrapping trust among microservices is challenging. In the case of the monolith, you do not need any component-to-component communication trust build. But on the other hand, service-to-service communication needs some trust in the form of mutual TLS or secure JWT.
- Sharing User Context – The distributed nature of microservices makes sharing user context harder. In a monolithic application, all internal components share the same web session, and anything related to the requesting party (or user) is retrieved from it. In the microservice architecture, the user context must be passed explicitly from one microservice to another. The challenge is to build trust between two microservices for the receiving microservices to accept the user context passed from the calling microservices. It would help if you knew a way to verify that the user context passed among microservices is not deliberately modified.
- Polyglot Services – Polyglot architecture demands more security expertise on each development team. As different teams use different technology stacks for development, each team must have its own security experts.
To highlight the various security patterns that I believe everyone dealing in microservices should be aware of, I have developed a series of blogs where I will discuss them. Once you are aware of these patterns, you can decide better as per the situation.
Patterns For Authentication & Authorization
To explain patterns around authentication and authorization I’ll take an example of e-commerce microservices application as shown below.
Consider a typical use case where the user can place an order-to-order service. Order service will fetch the address mentioned in the order request from the address service. Once an order is placed successfully order service will coordinate with the inventory service for decreasing the product inventory. Inventory service will internally coordinate with the audit service for maintaining the audit logs.
This example application will give us various scenarios to explore authentication and authorization patterns like how user authentication will happen, how service will pass user context, how service to service authentication & authorization will happen etc.
Order Processing Service, is responsible for process the order placed by a user.
Customer Info Service, will manage the user’s address.
Inventory Service, is responsible for maintaining the product inventory.
Audit Service, will maintain the audit logs for changes done in product inventory.
Now, let’s check out the security authentication patterns that you can look for in your microservice architecture.
Authentication pattern is about various patterns that help in recognizing a user or system’s identity.
OIDC (OpenID Connect) for user authentication
OpenID Connect is a profile built on top of OAuth 2.0. OAuth 2.0 is about access delegation, while OpenID Connect is about authentication. These days OpenID connect is de facto for providing authentication. An OAuth 2.0 authorization server that supports OpenID Connect returns an ID token, JSON Web Token (JWT), and the access token. You can look at an ID token as an authentication token and an access token as an authorization token. ID token proves that the user is authenticated, ID token will have some basic information about the user like first & last name, userID, username, etc. It supports three flows.
Authorization code flow uses the same flow as the OAuth 2.0 Authorization Code grant type. It returns an authorization code like OAuth 2.0 that can be exchanged for an ID, access, and refresh tokens. It is suitable for applications with secure storage required to exchange authorization code for ID and access token. We can also use this flow with Proof-Key for Code Exchange (PKCE) to prevent authorization code injection.
Hybrid flow: The ID token is returned from the initial request alongside an authorization code in the hybrid flow.
Implicit flow: It is similar to what is defined by OAuth 2.0, but this flow is highly discouraged.
There is no equivalent to client credentials flow in OpenID connect, which makes sense as it does not require user authentication.
The following sequence flow diagram explains OIDC:
Now front-end application has ID token, which is a proof of user successful authentication. The way it is leveraged in microservice architecture is explained further in the following patterns.
Direct authentication with the trusted subsystem
User authentication happens only at the front-end web application via Auth Server (OIDC) or through web application itself by providing another mechanism like basic authentication. Once the user gets authenticated, the web application passes the logged-in user’s identifier to the back-end APIs to retrieve data related to the user.
Since both the web application and the APIs are in the same trust domain, we only authenticate the end-user at the web application. The back-end APIs trust whatever data gets passed on to those from the web application, and it is called the trusted subsystem pattern.
This pattern is considered anti-pattern nowadays. The opposite of the trust-the-network pattern is the zero-trust network. With the zero-trust network pattern, we do not trust the network; we need to make sure we have enforced security checks as much as closer to the resource (or, in our case, the APIs)
Zero-trust network pattern with opaque or reference token
Applications that have not incorporated any standard way for user authentication like OIDC, OpenId 2.0, OpenIdConnect, SAML, etc., create their own way to authenticate the user. The high-level steps are similar to what we discuss under OIDC, and the only difference is that the token we got is a reference token instead of JWT, which means they are not self-sufficient. You need an Auth server to tell if the token is valid or not and what user information it holds.
The drawback of this approach is that:
- Auth server gets overloaded with unnecessary calls.
- Auth server becomes the single point of failure.
- As every request needs to be coordinated with the Auth server, latency will increase.
So, implementing a Zero-trust network pattern with an Opaque token is not advisable.
Zero-trust network pattern with ID token (JWT)
OIDC specification specifies the format of the ID token by leveraging the JWT specification, which, unlike the access token in OAuth 2.0, is not opaque. It has a well-specified format, and the client can directly read the values (called claims) within the token. OpenID provider must also sign the ID token, as defined JWS (JSON web signature) specification. It allows the clients to discover information about the authenticated user in a standard way.
The ID token in JWT format and with a digital signature makes it possible that the backend can verify the token and read the contents without a request to Auth Server, resulting in less demand on the Auth server and lower latency when processing requests.
The following diagram visualizes what happens when the frontend sends a request to the backend:
- The backend retrieves Auth Server’s public keys. The backend does not need to do this for all requests but can cache the keys in memory.
- Front end will now send a request to the backend that includes the ID token.
- The backend uses the public keys retrieved earlier to verify that the ID token was issued by a trusted Auth server and then confirms that the token is valid.
- The backend returns the results to the frontend.
Service to service authentication using basic authentication
Basic authentication provides users or systems with a valid username and password to access microservices via an API. It will enable you to pass the base64-encoded username and password in the HTTP authorization header and a request to an API. The following curl example shows how to pass basic authentication using curl client.
This authentication mechanism fails in the case of microservices deployment due to the following reasons:
- The application needs to retain the information for a long time if the basic authentication is used. It is quite irritating to authenticate an application frequently to perform operations. The chances of compromise increase with the inclusion of longer information.
- There are not any restrictions on application’s working. Once the application accesses the username and password of the user, it can do everything that the user can do with microservices.
Service to service authentication using Mutual TLS (mTLS)
Mutual Transport Layer Security is a method through which a server is verified by a client application. Even the client application gets verified by server through the exchange of certificates and allow each one to own the corresponding private keys. Certificates in MTLS are time-bound and once it is expired then it is no longer considered valid. We can say that it is better than the basic authentication. On the other hand, mTLS don’t offer a mechanism to represent the end-user using the corresponding application. You can leverage mTLS to authenticate your client’s application talking to the microservices but not representing the end-user.
In this blog, I have extensively talked about the microservice architecture’s authentication pattern. The upcoming blogs will cover the authorization patterns and API gateway related to security. Do check those blogs as well till then, happy coding!