Everyone’s excited about microservices, but actual implementation is sparse. Perhaps the reason is that people are unclear on how these services talk to one another; especially tricky is properly maintaining identity and access management throughout a sea of independent services.
Unlike a traditional monolithic structure that may have a single security portal, microservices pose many problems. Should each service have it’s own independent security firewall? How should identity be distributed between microservices and throughout my entire system? What is the most efficient method for the exchange of user data?
There are smart techniques that leverage common technologies to not only authorize but perform delegation across your entire system. In this article we’ll identify how to implement OAuth and OpenID Connect flows using JSON Web Tokens to achieve the end goal of creating a distributed authentication mechanism for microservices — a process of managing identity where everything is self-contained, standardized, secure, and best of all — easy to replicate.
What Are Microservices, Again?
For those readers not well-versed in the web discussion trends of late, the microservice design approach is a way to architect web service suites into independent specialized components. These components are made to satisfy a very targeted function, and are fully independent, deployed as separate environments. The ability to recompile individual units means that development and scaling can be vastly easier within a system using microservices.
This architecture is opposed to the traditional monolithic approach that consolidates all web components into a single system. The downside of a monolithic design is that version control cycles are arduous, and scalability is slow. The entire system must be continuously deployed since it’s packaged together.
The move toward microservices could have dramatic repercussions across the industry, allowing SaaS organizations to deploy many small services no longer dependent on large system overhauls, easing development, and on the user-facing side allowing easy pick-and-choose portals for users to personalize services to their individual needs.
The Good, Bad, and What You Should Be Doing Better: Read Our Post on Microservice Best Practices
Great, So What’s The Problem?
The problem we’re faced with is that microservices don’t lend themselves to the traditional mode of identity control. In a monolithic system security works simply as follows:
- Figure out who the caller is
- Pass on credentials to other components when called
- Store user information in a data repository
Since components are conjoined within this structure, they may share a single security firewall. They share the state of the user as they receive it, and may also share access to the same user data repository.
If the same technique were to be applied to individual microservices, it would be grossly inefficient. Having an independent security barrier — or request handler — for each service to authenticate identity is unnecessary. This would involve calling an Authentication Service to populate the object to handle the request and respond in every single instance.
The Solution: OAuth As A Delegation Protocol
There is a method that allows one to combine the benefits of isolated deployment with the ease of a federated identity. Jacob Ideskog of Curity believes that to accomplish this OAuth should be interpreted not as Authentication, and not as Authorization, but as Delegation.
In the real world, delegation is where you delegate someone to do something for you. In the web realm, the underlying message is there, yet it also means having the ability to offer, accept, or deny the exchange of data. Considering OAuth as a Delegation protocol can assist in the creation of scalable microservices or APIs.
To understand this process we’ll first lay out a standard OAuth flow for a simple use case. Assume we need to access a user’s email account for a simple app that organizes a user’s email — perhaps to send SMS messages as notifications. OAuth has the following four main actors:
- Resource Owner (RO): the user
- Client: the web or mobile app
- Authorization Service (AS): OAuth 2.0 server
- Resource Server (RS): where the actual service is stored
A Simplified Example of an OAuth 2 Flow
In our situation, the app (the Client), needs to access the email account (the Resource Server) to collect emails before it can organize them to create the notification system. In a simplified OAuth flow, an approval process would be as follows:
- The Client requests access to the Resource Server by calling the Authorization Server.
- The Authorization Server redirects to allow the user to authenticate, which is usually performed within a browser. This is essentially signing into an authorization server, not the app.
- The Authorization Server then validates the user credentials and provides an Access Token to client, which can be use to call the Resource Server
- The Client then sends the Token to the Resource Server
- The Resource Server asks the Authorization Server if the token is valid.
- The Authorization Server validates the Token, returning relevant information to the Resource Server i.e. time till token expiration, who the token belongs too.
- The Resource Server then provides data to the Client. In our case, the requested emails are unbarred and delivered to the client.
An important factor to note within this flow is that the Client — our email notification app — knows nothing about the user at this stage. The token that was sent to the client was completely opaque — only a string of random characters. Though this is a secure exchange, the token data is itself useless to the client. The exchange thus supplies access for the client, but not user information. What if our app needed to customize the User Experience (UX) based on which membership level the user belonged to, a group they were a member of, where they were located, their preferred language, etc.? Many apps provide this type of experience and for that they require additional user information.
The OpenID Connect Flow
Let’s assume that we’re enhancing the email service client so that it not only organizes your emails, but also stores them and translates them into another language. In this case, the client will want to retrieve additional user data and store it in it’s own user sessions.
To give the client something other than the opaque token provided in the OAuth flow, use an alternative flow defined in OpenID Connect. In this process, the Authorization Server, which is also called an OpenID Connect Provider (OP), returns an ID Token along with the Access Token to the client. The flow is as follows:
- The Client requests access to the Resource Server by calling the Open ID Connect enabled Authorization Server.
- The Authorization Server redirects to allow the user to authenticate.
- The Authorization Server then validates the user credentials and provides an Access Token AND an ID Token to the client.
- The Client uses this ID Token to enhance the UX and typically stores the user data in it’s own session.
- The Client then sends the Access Token to the Resource Server
- The Resource Server responds, delivering the data (the emails) to the Client.
The ID token contains information about the user, such as how they authenticated, the name, email, and any number of custom data points on a user. This ID token takes the form of a JSON Web Token (JWT), which is a coded and signed compilation of JSON documents. The document includes a header, body, and a signature appended to the message. Data + Signature = JWT.
Using a JWT, you can access the public part of a certificate, validate the signature, and understand that this authentication session was issued — verifying that the user has been authenticated. An important facet of this approach is that ID tokens establish trust between the Authorization Server/Open ID Connect Provider and the Client.
Using JWT For OAuth Access Tokens
Even if we don’t use OpenID Connect, JWTs can be used for many things. A system can standardize by using JWTs to pass user data among individual services. Let’s review the types of OAuth access tokens to see how to smartly implement secure identity control within microservice architecture.
By Reference: Standard Access Token
This type of token contains no information outside of the network, simply pointing to a space where information is located. This opaque string means nothing to user, and as it is randomized cannot easily be decrypted. This is the standard form of an access token — without extraneous content, simply used for a client to gain access to data.
By Value: JSON Web Token
This type may contain necessary user information that the client requires. The data is compiled, and inserted into the message as an access token. This is an efficient method because it erases the need to call again for additional information. If exposed over the web, a downside is that this public user information can be read easily read, exposing the data to an unnecessary risk of decryption attempts to crack codes.
The Workaround : External vs. Internal
To limit this risk of exposure, Iskedog recommends splitting the way the tokens are used. What is usually done is as follows:
- The Reference Token is issued by the Authorization Server. The client sends back when it’s time to call the API.
- In the middle: The Authorization Server validates the token and responds with a JWT.
- The JWT is then passed further along in the network.
In the middle we essentially create a firewall, an Authorization Server that acts as a token translation point for the API. The Authorization server will translate the token, either for a simple Reverse Proxy, or a full scale API Firewall. The Authorization Server shouldn’t be in the “traffic path” however — the reverse proxy finds the token and calls the Authorization server to translate it.
Let All Microservices Consume JWT
So, to refresh, with microservice security we have two problems:
- We need to identify the user multiple times: We’ve shown how to leave authentication to OAuth and the OpenID Connect server, so that microservices successfully provide access given someone has the right to use the data.
- We have to create and store user sessions: JWTs contain the necessary information to help in storing user sessions. If each service can understand a JSON web token, then you have distributed your identity mechanism, allowing you to transport identity throughout your system.
In microservice architecture, an access token should not be treated as a request object, but rather as an identity object. As the process outlined above requires translation, JWTs should be translated by a front-facing stateless proxy, used to take a reference token and convert it into a value token to then be distributed throughout the network.
Why Do This?
By using OAuth with OpenID Connect, and by creating a standards based architecture that universally accepts JWTs, the end result is a distributed identity mechanism that is self contained and easily to replicate. Constructing a library that understands JWT is a very simple task. In this environment, access as well as user data is secured. Creating microservices that communicate well and securely access user information can greatly increase agility of the whole system, as well as increase the quality of the end user experience.