Stateless security with MicroProfile JWT

For a while, companies used to store user contextual information in HTTP Sessions. It has worked for years, but in a clustering architecture, it proved to be expensive, unreliable, and painful to scale. However, with microservices and REST, which are stateless, HTTP Session state is not used eliminating the problem of sharing session state. The question is: How and where to save security context?

In this post, I'll explain Eclipse MicroProfile, JSON Web Tokens (JWT), and how they can be used to implement stateless security. I'll also talks about the extensibility and flexibility of MicroProfile with claims.

What is Eclipse MicroProfile?

Eclipse MicroProfile defines itself as

  • The MicroProfile is a baseline platform definition that optimizes Enterprise Java for a microservices architecture and delivers application portability across multiple MicroProfile runtimes. -- MicroProfile FAQ

Java for Enterprise applications are usually built on two options: Spring Framework and Java EE. Java EE created a set of specifications defined first by Sun Microsystems and then by Oracle through the Java Community Process. Specifications were meant to facilitate vendor agnostic development and deployment. During the last 5 years, the Java EE platform has become stable and mature resulting in less frequent releases. Java EE has also expanded over the years, requiring vendors to maintain or implement the large set of specifications.

While Java EE was slowing down, web services continue to evolve leading to the creation of new technologies such as JSON, HTTP 2, RESTful web services and microservices architecture. With its slower release cycle, Java EE failed to keep up with changes in the industry. Aware of the skills and investment that both enterprises and vendors put into Java EE, a group of vendors, supported by the active Java Community, decided to create MicroProfile, an optimized platform for microservices architecture.

MicroProfile was created in 2016 and quickly joined the Eclipse foundation. Since then, there have been 5 releases of the MicroProfile platform with the addition of many specifications to addressing the needs and feedback of users (See Figure 1). MicroProfile JWT, one of the most recent specifications, addresses the authentication and authorization of microservices using JWT. MicroProfile as per June 2018 release, is composed of the following specification.

Figure 1: MicroProfile 1.4 release content.

What is JWT?

JWT stands for JSON Web Token. It's a JSON-based text format for exchanging information between parties. JWT is an open standard specified under RFC 7519. The information contained in the JWT is called claims and the JWT is usually digitally signed (i.e. JSON Web Signature) so that the information can be verified and trusted. Optionally, it's also possible to encrypt the claims (i.e. JSON Web Encryption) so it's not in clear text within the JWT.

JWT is widely used because it is simple and efficient. One use cases is exchanging authentication and authorization information between parties so that a remote server can identify the caller, verify the caller's identity by checking the signature, and implement Role Based Access Control based on roles included in the JWT call. Standards such as OpenID Connect and OAuth 2 use JWT to represent their own tokens.

A JWT is composed of:

  • Header: the header contains metadata such as the type of algorithm used to sign the token (HS256 for HMAC for instance, RS256 for RSA, ES256 for Elliptic Curves), the type of the token (OpenID Connect, OAuth2, MicroProfile JWT), etc
  • Claims: the claims is basically all the information you want to store in the token. Some are required depending on the type of token.
  • Signature: the signature, although optional, it is highly recommended. If you want to trust something coming from the outside, you need the signature. It allows you to know the content hasn't been changed by the caller or in between (man in the middle). I don't recommend using MicroProfile JWT without a digital signature.

In its serialized version, a JWT token looks like:

base64(header).base64(claims).base64(signature)

As an example:

eyJraWQiOiJteS1yc2Eta2V5IiwiY3R5IjoianNvbiIsInR5cCI6IkpXVCIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiJhbGV4IiwidG9rZW4tdHlwZSI6ImFjY2Vzcy10b2tlbiIsImlzcyI6Ii9vYXV0aDIvdG9rZW4iLCJncm91cHMiOlsiY3JlYXRlIiwidXBkYXRlIiwiZGVsZXRlIl0sIm5iZiI6MTUzMzIyODUxOSwiZXhwIjoxNTMzMjI4ODE5LCJpYXQiOjE1MzMyMjg1MTksImVtYWlsIjoiYWxleEBzdXBlcmJpei5jb20iLCJqdGkiOiI0ODAxODQ4MDFmNzgyOGNhIiwidXNlcm5hbWUiOiJhbGV4In0.PZWPE-bXNzKbO6aoEqWxE....apj8sxtIBP1rgFIU8ZQ

In it's JSON format, a JWT token looks like this for the header

{
  "kid": "my-rsa-key",
  "cty": "json",
  "typ": "JWT",
  "alg": "RS256"
}

And like this for the payload

{
  "sub": "alex",
  "token-type": "access-token",
  "iss": "/oauth2/token",
  "groups": [
    "create",
    "update",
    "delete"
  ],
  "nbf": 1533228519,
  "exp": 1533228819,
  "iat": 1533228519,
  "email": "alex@superbiz.io",
  "jti": "480184801f7828ca",
  "username": "alex"
}

Implementing stateless security with MicroProfile JWT?

MicroProfile JWT is a specification aiming at defining a simple and common way to rely on JWT to implement authentication and authorization for microservices. Implementing security usually involves 3 main steps:

  • Authenticate the caller: identify the caller by reading a standard claim (e.g. username) in the JWT and validate the signature of the JWT
  • Authorize the caller: by using the roles of the group listed in the claim, enforce access control in the application
  • Propagate caller context: pass the JWT token in subsequent calls so other microservices involved can also service the request

MicroProfile JWT defines a minimum set of claims such as: it is usable as an authentication token and it is usable as an authorization token containing high level roles. MicroProfile JWT also supports extensibility through the defined IANA JWT Assignments (pre-defined set of claims) or any custom claims. In addition to that, MicroProfile JWT also defines two new claims: 'upn' to identify the subject and 'groups' to hold all the roles to be mapped in the application.

MicroProfile JWT completely fulfills the requirements of microservices architecture and solves the HTTP Session clustering issue by pushing the state on the caller side as opposed to maintaining the state on the server. On the server side, it becomes trivial to pass the security context (aka the JWT) either as a header, a cookie or in the payload.

Figure 2, shows the big picture of how it looks like all together.

Figure 2: typical architecture of a system using JWT

If you want to know about more MicroProfile JWT, visit
https://www.eclipse.org/community/eclipse_newsletter/2017/september/article2.php

The Pros and Cons to using JWT

There are some downsides to using JWT to store session context including:

  • Base64 encoding isn't encryption: Because base64 is not encryption everyone in the middle or the caller itself can, at any point, read the content of the JWT. If you want to protect your data, you also need to encrypt part or the whole JWT using JSON Web Encryption another standard like JSON Web Signature but for encryption rather than signing.
  • Larger Payload:even in its most minimal form, a serialized JWT is way bigger than a regular cookie or SessionID. Even though nowadays bandwidth is less of an issue, it's something to keep in mind. The more you put in the JWT, the more you have to send with each and every communication on the wire.
  • Token Expirationkeep in mind that the token can be valid but may contain outdated information. Make sure to properly configure the expiration policy of your token so that a stale token can be detected and refreshed.

On the other hand, there are a lot of benefits to using JWT

  • JSON: because it's JSON based, it's simple and lightweight to deal with and there are plenty of libraries for it
  • Speed and Reliability: it does not require a third party call which in the context of a distributed system (microservices for instance) can be slow and create a single point of failure (either LDAP, Database, or your identity provider).
  • Secure: it is secure if you are at least using JSON Web Signature and possibly JSON Web Encryption
  • Claims: it easy for parties to agree on a common set of claims for performing authentication, authorization and more

MicroProfile JWT defines a common integration with Java EE allowing efficient use of JWT in Java EE development. For instance, it defines a standard javax.security.Principal interface to access the main claims and it integrates with Context and Dependency Injection (CDI) and has the ability to inject all or a specific claim.

How to extend user context with custom claims?

When using JWT for authentication and authorization, it's important to be able to add custom claims to the JWT, otherwise, you will end up having to maintain state on the server side with the additional information, or do an extra call passing in the JWT. If you are looking at an identity provider to authenticate the user and deliver JWT, you should be seriously considering support for extensible custom claims.

Basically, the idea is to customize the JWT with user information such as email, language, and subscription details, so you can then use the trustable information in the JWT during processing. You can for instance decide to route gold subscriptions to a dedicated set of servers.

To conclude, MicroProfile JWT makes use of most of JWT standard to simplify authentication and authorization in the context of microservices. It provides a common set of claims and a default behavior for JAX-RS and CDI so you can focus on the implementation of your business logic.

About the Author

Jean-Louis Monteiro
Tomitribe