Microservices Security Design Patterns
This is the 11th post in a series on microservices architecture. This article is originally published at https://www.learncsdesign.com
Microservice architectures are distributed architectures. Each external request is handled by a gateway and one or more services. The services must implement some aspects of security. To implement security in a microservice architecture, we need to determine who is responsible for authenticating a user and who is responsible for authorizing them.
Below mentioned two aspects prevent us from simply copying the security design from monolithic architecture:
- In-memory security context — In-memory security contexts such as ThreadLocal are used to pass around user identities. Services cannot share a memory, so they can’t use an in-memory security context to pass user identities around. We need a different mechanism for passing user identities from one microservice to another in a microservice architecture.
- Centralized Session — An in-memory security context makes no sense, and neither does an in-memory session. In theory, multiple services could access a database-based session. However, this would violate the loose coupling principle. A microservice architecture requires a different session mechanism.
Authentication in a microservices architecture
Authentication protects your system against spoofing by identifying the requesting party.
Authentication can be handled in a few different ways. Authentication can be handled by individual services. The problem with this approach is that it allows unauthenticated requests to enter the internal network. Each development team is responsible for properly implementing security across all of their services. Consequently, an application may contain significant security vulnerabilities.
Authentication in the services is also problematic in that different clients authenticate in different ways.
- In pure API clients, credentials are supplied with each request using, for example, basic authentication.
- Other clients might log in first and then supply a session token with each request.
We want to avoid requiring services to manage a diverse set of authentication mechanisms.
API gateways should authenticate requests before forwarding them to services. Centralizing API authentication in the API gateway has the advantage that there is only one place to get it right. Consequently, the chances of a security vulnerability are much lower. Furthermore, only the API gateway has to deal with the various authentication mechanisms. The services are not exposed to this complexity.
Access Token Pattern
The API gateway authenticates clients. API clients include credentials in their requests. Login-based clients send their credentials to the API gateway’s authentication and receive a session token in response. Once the API gateway authenticates a request, it invokes one or more services.
A service invoked by the API gateway needs to know the request’s principal. It must also verify that the request is authenticated. The API gateway should include a token with each service request. By using the token, the service validates the request and obtains information about the principal. API gateways might also provide session-oriented clients with the same token to use as a session token.
Authentication flow for API clients:
- An API client sends a request containing credentials.
- API gateways authenticate credentials, create a security token, and pass that to the service.
Authentication flow for login-based clients:
- A client submits a login request containing credentials.
- A security token is returned by the API gateway.
- The client forwards the security token to the service.
Authorization in a microservices architecture
The authentication of a client’s credentials is important but not sufficient. An application must also implement an authorization mechanism to ensure that the client is authorized to perform the operation.
Request authorization can be implemented in two places:
- API Gateway — If a user is not allowed to access a particular path, the API gateway may reject the request before forwarding it to the service. Authorizing in one place also minimizes security risks. API gateway authorization can be implemented using a security framework such as Spring Security. API gateways can usually implement role-based URL access, but not ACLs that control access to individual domain objects.
- Services — It is possible for a service to implement role-based authorization for URLs and for methods. The service can also implement ACLs to manage aggregate access.
In a microservice architecture, you need to decide what type of token an API gateway should use to pass user information to the services.
There are two types of tokens available:
- Opaque tokens — Tokens that are opaque are typically UUIDs. A disadvantage of opaque tokens is that they reduce performance and availability and increase latency. The recipient of such a token must make a synchronous RPC call to a security service to validate the token and retrieve user information.
- Transparent tokens — Transparent tokens eliminate the need for RPC calls to security services that contain user information. JSON Web Token (JWT) is one such popular transparent token.
JWTs to pass identity and roles
As part of the JWT standard, claims such as user identities and roles can be securely represented between two parties. JWTs have a payload, which is a JSON object containing information about the user such as their identity and roles, as well as other metadata such as expiration dates. It’s signed with a secret that’s only known by the creator of the JWT, like the API gateway or recipient service. JWTs can’t be altered or forged by malicious third parties due to the secret.
Drawbacks of JWT
- A token cannot be revoked and can become invalid only after its expiration date.
This problem can be resolved by issuing JWTs with short expiration times, but then the application must continuously reissue JWTs to maintain the session, which can be solved by using OAuth 2.0 security standard.
Security Standard OAuth 2.0
Rather than developing security infrastructure, you can use an off-the-shelf service or framework that implements a standard called OAuth 2.0.
The Internet Engineering Task Force (IETF) OAuth working group developed OAuth 2.0. OAuth 2.0 addresses the access delegation issue.
Access delegation problem
You must delegate the corresponding access rights to an application or person if you want them to access and use a resource on your behalf.
Access delegation can be categorized into two models:
- Access delegation via credential sharing
- Access delegation with no credential sharing.
In the first model, we must share our credentials with third-party applications, which can be extremely risky.
Conceptually, both OAuth 1.0 and OAuth 2.0 fix the access delegation problem. The main difference between OAuth 2.0 and OAuth 1.0 is that OAuth 2.0 is more extensible. While OAuth 1.0 is a protocol, OAuth 2.0 is an authorization framework.
OAuth 2.0 is based on the following concepts:
- Authorization server — Provides an API for authenticating users and getting an access token and a refresh token. Spring OAuth is an example of a framework used for building an OAuth 2.0 authorization server.
- Access token — Tokens that grant access to a resource server. As with JWTs, the format of the access token is implementation-dependent.
- Refresh token — Clients obtain a new access token by using a long-lived, yet revocable, token.
- Resource server — An access token is used to authorize access to a service. The services in a microservice architecture are resource servers.
- Client — A client that requests access to a resource server. The API gateway is the OAuth 2.0 client in a microservice architecture.
API gateway authentication flow for an API client
API gateways authenticate API clients by sending a request to the OAuth 2.0 authorization server, which returns an access token. An API gateway then sends one or more requests to the services containing the access token.
- A client submits a request supplying its credentials using basic authentication.
- API gateway sends an OAuth 2.0 password grant request to the OAuth 2.0 authorization server.
- The authorization server validates API client credentials and returns an access token and refresh token.
- In the requests it makes to the services, the API gateway includes the access token. The service validates the access token and uses it to authorize the request.
API gateway authentication flow for session-oriented client
The API gateway can authenticate session-oriented clients by using an OAuth 2.0 access token as a session token. API clients initiate sessions by POSTing their credentials to the API gateway endpoint. API gateways return access tokens and refresh tokens to clients. When the API client makes a request to the API gateway, it provides both tokens.
- Login-based clients POST their credentials to the API gateway.
- API gateways make OAuth 2.0 password grant requests to OAuth 2.0 authorization servers.
- The authorization server validates the credentials of the client and returns an access token and a refresh token.
- API gateways, for example, return access and refresh tokens to the client through cookies.
- In its requests to the API gateway, the client includes the access token and the refresh token.
- API gateways include the access token in their requests to the services after validating them.
As soon as the access token expires or is about to expire, the API gateway obtains a new access token by sending an OAuth 2.0 refresh grant request that contains the refresh token to the authorization server. The authorization server returns a new access token if the refresh token has not expired or been revoked. API gateways pass the new access token to the services and return it to clients.
The refresh token flow is described in the OAuth 2.0 specification document.
(A) The client requests an access token by authenticating with the authorization server and presenting an authorization grant.
(B) The authorization server authenticates the client and validates the authorization grant, and if valid, issues an access token and a refresh token.
(C) The client makes a protected resource request to the resource server by presenting the access token.
(D) The resource server validates the access token, and if valid, serves the request.
(E) Steps (C) and (D) repeat until the access token expires. If the client knows the access token expired, it skips to step (G); otherwise, it makes another protected resource request.
(F) Since the access token is invalid, the resource server returns an invalid token error.
(G) The client requests a new access token by authenticating with the authorization server and presenting the refresh token. The client authentication requirements are based on the client type and on the authorization server policies.
(H) The authorization server authenticates the client and validates the refresh token, and if valid, issues a new access token (and, optionally, a new refresh token).
Benefits of OAuth 2.0
- The OAuth 2.0 standard is a proven security standard.
- If you use an off-the-shelf OAuth 2.0 authentication server, you don’t have to implement it.
Securing communications among microservices
Using JSON Web Token (JWT), we have already seen how to share contextual data among microservices.
We will now see how to secure the communication between microservices over HTTP with mutual Transport Layer Security (mTLS).
Communication between two parties is protected by TLS for confidentiality and integrity. TLS has been used to secure data in transit for several years. In addition to protecting data in transit for confidentiality and integrity, TLS helps a client application identify the server it is interacting with. Certificates represent the corresponding server’s public key and bind it to a common name signed by a certificate authority (CA).
The TLS protocol itself is also known as one-way TLS, mainly because it helps the client identify the server it’s communicating with but not the other way around. mTLS, or mutual TLS, fills this gap by helping the client and server identify each other. The server knows which client it’s talking to in mTLS just as the client knows which server it’s talking to in one-way TLS.
When mTLS is used to secure communications between microservices, each microservice can legitimately identify who it talks to, as well as ensure the integrity of data in transit between the two microservices.
Below are links to posts that explain each pattern in more detail.
1. Monolithic vs Microservices Architecture
2. Microservices Design Principles
3. Microservices Design Patterns
4. Microservices Decomposition Design Patterns
5. Microservices Data Design Patterns
6. Microservices Communication Design Patterns
7. Microservices External API Integration Patterns
8. Microservices Observability Design Patterns
9. Microservices Service Discovery Design Patterns
10. Microservices Cross-Cutting Concerns Design Patterns
11. Microservices Security Design Patterns
12. Microservices Deployment Design Patterns
If you like the post, don’t forget to clap. If you’d like to connect, you can find me on LinkedIn.
References
https://microservices.io
Microservices Security in Action — Manning Publications