Patterns in Microservices Authentication with Client Certificates
Recently, I was working with a customer on an issue involving a Wildfly 16 microservices mesh. The customer had multiple Wildfly containers running disparate workloads in standalone mode. If you’re a Java EE developer who’s familiar with some of these technologies, you can think of this as a heavy, EE-based way of doing a Spring Boot microservices mesh, complete with access to EJB, MDB, transactions, caching, JSF, WebSockets, management features, and other patterns — all out of the box.
One of the challenges this customer was facing was working with machine accounts, specifically calling from one Wildfly instance to another, to ensure secure microservices authentication. They wanted to show they could securely prove the identity of an individual Wildfly container to make machine-account-style calls against other APIs. We accomplished this by using Wildfly Elytron to enable client certificate authentication via Mutual Transport Layer Security (Mutual TLS or MTLS).
Why Machine Accounts Are Necessary for Microservices Authentication
If you’ve never written a microservice-style application before, the best way to describe the necessity of machine-account (or service-account) API-call credentials is through an example. Let’s say we’re working on a microservice where the domain is related to the User and Group objects. Users have properties like names, IDs, passwords, and profile pictures, and are added to groups that have similar properties. I might write services in the microservice app that create, read, update, and delete from a database, or table, with credentials specific to my microservice. What happens when a user needs to, for example, check out equipment from the equipment microservice? What if there needs to be a utility method written for the profile page that lists each user’s checked-out equipment? Even more complicated, which group has which equipment checked out, and who is the manager of that group as a point of contact?
Keep Business Logic on the Back-End
Suddenly, the architecture team is being asked to make a call: Do I push my business logic to the single page application, and keep my microservices “pure?” After all, it would be so easy to just “do this in Angular.” The correct architectural decision for microservices authentication is keeping business logic on the back-end, away from the front-end developers, who genuinely want to worry more about CSS and less about business needs as rendered in RxJS. I would also argue that it’s not muddying the waters or “impure” to have microservices calling other microservices from the back-end. In fact, it’s a well-known pattern across the enterprise, and it’s why we have terms like machine account and service account.
Should You Use JWT or Mutual TLS?
So, how do you implement a machine account? One way is to utilize the existing token architecture that you’re probably already using on your front-end: JSON Web Tokens (JWT). They’re cryptographically secure, easy to work with, and hey, you’re already using them! Why not just grant a special privilege in your code to [email protected] with a special password on your authentication service, and just give it a really long timeout so that it’s not constantly hammering the login system?
One reason for this is that your authentication library probably doesn’t have a usable way of revoking a JWT after it’s been granted without a stop-the-world systems disruption. This is why there’s a strong preference for MTLS as an identity provider for machine accounts accessing HTTPS resources. If you’re interested in learning more about how you can try this out yourself, check out the white paper: Secure Your Container-Based Microservices with Client Certificate Authentication.