Contributing Authors: Jamie Niswonger & David Pitt
I’ve been in the software development business for a long time and I can’t tell you how many login screens with authentication logic I have implemented. You might say that one of the most prevalent user stories is the need to log in and securely authenticate a user’s access to an application.
Here at Keyhole Software, we have implemented countless login and authentication approaches for applications, along with simple to sophisticated authorization schemes enforcing access control of applications. Of course, you can utilize the single sign-on type of technologies such as OAuth or OpenID, which offload the development of a login UI and the logic for authentication/authorization. However, these standards are not always utilized in enterprise environments. Many enterprises will have a single authentication mechanism that exploits a federated operating system network such as LDAP. A login UI still has to be created and authorization rules still have to be applied to each application.
Over the last few years, we have helped organizations transition away from monolithic-based applications to isolated microservice-based architectures. With Microservices, authentication and authorization logic is now spread across many decoupled distributed processes. It was a bit simpler with monolithic architectures as only a single process is authenticated and contains access control rules defined.
In this blog, we discuss a design pattern for authorization and authentication for use in a distributed microservices environment.
Auth-N and Auth-Z
Before we dive into the specifics, here are a couple of definitions we’ll use throughout this article:
- Auth-N is a term used for authentication of a user’s identity.
- Auth-Z refers to what the user is authorized to do.
The diagram below is a conceptual diagram of a Single-Page Application (SPA) that is driven by a Microservice architecture. This architecture utilizes an “edge” service, that provides “security” and “routing” in front of the microservice infrastructure downstream. The actual Auth-N/Auth-Z processing would be performed by an “auth” service.
When the browser-resident SPA authenticates (i.e Auth-N), it will call through the “edge”, which will delegate to the Auth service. The Auth service simply “authenticates” against the supplied credentials (ie. username/password), and returns an access token to the SPA. You’ll notice this only goes through the edge and has not yet engaged the downstream microservice(s) (or API gateway service).
Here’s a detailed sequence diagram of the Auth-N flow:
A valid Access token can be a random unique (opaque) token that has no intrinsic meaning. oAuth or OpenID access will work. You could also implement a homegrown mechanism or existing credential access mechanism (i.e LDAP) to validate the credentials.
Two-factor authentication can also be introduced at this stage. A reasonable timeout request should be applied to this access token and is used by the SPA produced by the authentication service. The access token is used only by the Auth service to validate access and will be replaced with a JWT token (non-opaque) for its journey to the downstream microservice infrastructure. This approach keeps the JWT token away from browser client applications.
Auth-Z
Determining “what” a user can view or what permissions they have is referred to as “Auth-Z”.
Essentially, the Auth-Z mechanism returns information that will be used to determine if the “caller” can perform the request they have made. This information could be some kind of OP code(s) that the Auth-Z mechanism stores and associates with a specific identified user (i.e. user ID), or a role assigned to users. Then as the request travels “downstream”, the “permissions” can be consumed to determine “authorization” at each service.
A consistent standardized way to get these “permissions” to an application is by encoding it into a JWT as claims. This is done at the Auth service since it is aware of a users identity, and can determine their permissions/roles.
- When an API request is made to the “edge” post authentication, the access token will be supplied and it will ask the Auth-N service for a JWT.
- This Auth service will verify the access token and return a JWT with “permissions” provided as claims.
- The edge will then “route” to the downstream service (API Gateway in this scenario), passing the JWT in an “Authorization” header.
Here’s a sequence diagram for this:
The JWT should be very short-lived; ideally being valid just long enough to ensure it can traverse the entire transaction path (multiple microservices could be involved). In this scenario, the JWT token is never visible to the client browser and will be valid for only one “transaction” initiated by the client.
Since the JWT token has encoded access and identity information, it can move from the API gateway through to the other service implementations, which can then apply and validate this information. Each service (ie. API Gateway and microservices) in the “transaction” path should verify the supplied JWT.
TLS Mutual Authentication of Distributed Services
So far, we’ve discussed how application users of a Microservice style applications are authenticated and authorized. As you can see, there are many “distinct” processes involved in the architecture which means communication between multiple “hosts.”
Traditionally, enterprises will use some kind of symmetric key-based authorization when authenticating one server process talking to another service process. This means a single secret is provided to accessing processes. Then, if this secret is ever compromised, it will be difficult to tell who the nefarious accessor is, forcing the secret to change and having to replace it on all participating entities (i.e. Auth, API Gateway, Services).
An asymmetric-based authentication mechanism involves using a PKI (Public/Private Key Infrastructure) utilizing a Public Key Cryptography to authenticate accesses processes on an individual basis. Each accessing process is granted access with a digital certificate that is produced using a public/private key. This type of asymmetric authentication approach is built into server TLS (transport layer security) mechanisms where digital certificates are used to authenticate access, as well as support encrypted communication on the wire.
The downside to this approach is that every request will have to perform the cryptographic logic to validate the request and public/private keys will have to be managed and deployed to all participating services. That said, we believe the performance hit and management tasks are outweighed by a secure system.
Conclusion
This Microservice Authentication/Authorization pattern can be applied in just about any technology platform. We have successfully done this using Java Spring/Boot frameworks. However, it can be applied successfully with .NET, JavaScript, Go, or any language that allows server-side endpoints that communicate over IP.
If you’d like to see a working example of this pattern, give us a call. We’d be happy to give you access and discuss your needs.
Also, for a deeper dive into Microservice architecture, see this Microservices Whitepaper we’ve put together.