The theme of loosely coupled independent software components underpins the rationale of the modern micro services architecture. Of course, this is nothing new: Encapsulation is one of the four principles of Object Oriented programming. Micro services seeks to extend this principle beyond the scope of code to the wider systems architecture. A Network of small, independent units of granular functionality, which adhere to common communication standards, are the natural progression from classic n-tier software systems.
Key to this independence is statelessness, REST principles suggest that each and every request should be independent of the next: There is little point in implementing separated software components only to bind them together again to meet constraints of other technologies which might require session state or a shared back end resource. An example of this is authentication and authorization. Too often we go to great lengths to accomplish a clean and simple system only to shoe-horn in a legacy authentication mechanism which introduces tighter coupling between the network of independent components.
JSON Web Token, used with an OAuth 2 flow, is a solution to this. JWT is based on asymmetric encryption and allows us to guarantee the authenticity of requests across the micro service network without having to create tight coupling between these services with session state or a central token store. JWT can be configured to carry custom state within the access token, removing the need for any user information to be stored within each independent application. This has clear benefits for security compliance, testing and redundancy issues. It ensures that we only need to on-board and manage users in one place. It also provides the option of completely outsourcing the identity management to a 3rd party such as
Okta,
AuthO or
Ping Identity.
In this example I will create my own OAuth 2 authorization server which can easily be enhanced to an enterprise scale identity management service using the rich features of Spring Security. A client application will provide the front end functionality which will be supported by a separate resource exposed as a REST API. Other than the private key, the resource server will be completely independent of the authorization server but will be able to make secure decisions for access control.
All of these components will be implemented with Spring Boot 2. This is ideally suited for a container based delivery into a micro service environment. Any number of resource services can be added to the network without the overhead of shared state or resource to manage authentication or authorization.
This exemplifies the principle of simple, clean, granular services which are easy to maintain and enhance to meet the rapidly changing demands from the business.
OAuth 2 Code Grant Flow
The goal of the OAuth Code Grant is to authorize a client application to access a resource on behalf of a resource owner without having to know the resource owner's security credentials. Although the process is not bound to any specific token implementation JWT compliments the Code Grant process to achieve a clean separation between the back end resources.
Before looking at the flow it's important to understand the four different actors in the authorization user-case.
- Resource Owner - the user who makes requests to the client via HTTP, usually with a browser.
- Authorization Server - The Identity Management service which hosts user identities and grants tokens to authenticated users to access other resources via a client
- Client Application - In the Code Grant flow the client is a web application which consumes resources from the resource server. It is important to understand that this is a server side web app and NOT a Javascript app executing in the browser. The Code Grant flow requires that the Client can be trusted as it retains a Client Secret value which it uses to authenticate it's request with the Auth' Server
- Resource Server - A REST API which produces and consumes representations of state from and to the client application. Multiple APIs may be implemented in a network micro services.
- Resource Owner, (User), opens a browser and navigates to Client app
- The client checks for a cookie
- If none is present it redirects to the hosted login page on the Authorization Server
- User provides authentication credentials, username and password
- Upon successful authentication returns a code, along with a state value, and redirects the browser back to the client app
- The Client app receives the code
- Verifies the state to check for CSRF
- Provides the code and secret to the Authorization server
- The Authorization server receives the code, authenticates the client's request using the secret, creates a token using the private key and returns it to the Client
- The Client receives the token and uses it to make subsequent requests to the resource server
- The Resource server receives requests, decrypts the token using the public key
Our Client application is assigned a scope, only those resources in scope are accessible by the resource. Individual users are granted authorities, most commonly implemented as roles. In Spring Security the Role Based Access Decision Manager interrogates the authenticated user's granted authorities. In JWT we can pass these through to the resource service in the claims and filter them as roles. We can also add anything from the domain model, such as organisation association, and maintain that information in the resource service by passing them through as custom claims in the token.
Spring Boot 2 Implementation
Now that we understand the OAuth process and the use-case we intend to solve, let's walk through a real example implemented with Spring Boot 2. All three independent services, can be found on Github -
Let's start by creating the Authorization Server. As always, we start with the build and bring in the Spring Boot dependencies into the
pom.xml.
The main configuration annotation sets up everything we need for the Authorization server, the hosted login page, web service and all the request and response logic of the OAuth flow.
For the purposes of this example the UserDetails are stored as in-memory attributes. This could easily be extended with a custom UserDetailsService backed by a store.
The same goes for the ClientDetails. For a real world implementation we would want to be able to manage the Clients via UI and as with the UserDetailsService Spring Security allows us to implement a custom ClientDetailsService and store details however we choose.
OAuth does not dictate any specific type, or management of, the token. Spring Security allows us to implement the token how we wish but provides extensions for JWT.
For simplicity we'll just set a static signing key, which will be used here and in the Resource Server to decode the token. In a real world implementation this would be an Asymmetric key and we'd generate the private part and export it to the resource service via a robust key management tool
As well as the converter, we need to create a token enhancer to add the custom claims to the access token. Here I'm just setting a static String value against the 'organization'. In reality our UserDetails are likely to be part of a relational schema which could also describe how the user relates to a wider organization account. This would be very important for access control of resources and this value could again come from the UserDetailsService.
Then add the converter and enhancer to the token store.
Client App
As with the authorization service, the Client application is configured purely from a single annotation
and the client settings are provided in the application properties
The Code Grant flow is best suited to server side web apps so this application uses Thymeleaf to render the values returned from the resource service on a secured page which is only accessible to an authenticated user. The rest of the configuration sets up the view controllers and resource handlers for the template html pages.
Resource Server
The resource server security is configured for the OAuth flow with another annotation.
We add the JWT token store and a custom converter to decode the token with the key and access the custom claims
The resource service is a simple REST service created with the Spring Web framework. In order to access the authentication details and authenticated principal in the controller we can simply include them as arguments in the method. The converter we configured earlier then adds the custom claims from the token to the object and we can pull back the details in the logic. I've also set up method security annotations so we can annotate the controllers with the Spring Security annotations and restrict access based on SpEL expressions. In this case I'm securing the method both by restricting the scope and the authenticated user's granted authorities.
Running the System
All three services are set to run on different ports. Using the Maven Spring-Boot plugin run each one on localhost and navigate to the Client on
http://localhost:8082
The browser doesn't yet have a cookie and so presents a page inviting the user to login. Clicking the login will redirect the browser to the Authorization Server's hosted login page on
http://localhost:8080/auth/login. Enter the username and password defined in the example in memory configuration and sign in.
The browser is now redirected back to the client app which will render the Secured Page. The resources for which are fetched from the resource server, running on http://localhost:8081 and substituted into the template.
Dangers of Stateless Authorization
The JWT's validity can only be ascertained by decryption with the private key. There is no way for the Authorization Server to revoke the key once issued. For this reason the key is only valid for a short period of time. Also, The attack surface of a system using JWT is large. If the private key is compromised all identities and resource servers are compromised.