Secure your JAX-RS APIs with MicroProfile JWT

In this article, I want to illustrate in a practical way how to secure your JAX-RS APIs with MicroProfile JWT (JSON Web Token). It is illustrated by a GitHub project using Quarkus, Wildfly, Open Liberty and JWTenizr.

A basic knowledge of MP JWT is needed and, if you don’t feel comfortable with that, I invite you to read MicroProfile JSON Web Token written by my friend Jean-Louis Monteiro from Tomitribe.

Technical context

The project has been developed in the following context:

  • OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.7+10)
  • MicroProfile 3.3
  • GraalVM 19.3.1 (for Quarkus Native)
  • Wildfly 20.0.0.Final
  • Quarkus 1.5.0.Final
  • OpenLiberty 20.0.0.7
  • JWTenizr 0.0.4-SNAPSHOT.

Project structure

The project is made of 3 independent modules. Each one is dedicated to a runtime: Quarkus, Wildfly, Liberty.

To keep it simple, there is deliberately nothing in common between these modules. This enables to adapt the code and the configuration with flexibility. As the saying goes: the evil is in the details. And IT make no exception! Even when using a standard API such as MP JWT, there are often some subtle differences when it comes to concrete implementation: configuration, coding, testing, dependencies … However I’ve tried to keep the 3 modules as close as possible.

Each project is made of a JAX-RS resource class named GreetingResource that exposes a (very) simple REST API. It has 4 methods, all of them returning a String value:

  • hello: permitted to everybody
  • securedHello: restricted to users with the duke role
  • forbidden: restricted to users with the root role
  • getMyClaim: return the value of a custom claim.

The API is documented using OpenAPI and exposed with swagger-ui.

As can be seen in the code, MP JWT programming model is fairly easy. It is based on annotations (@DenyAll, @PermitAll, @RolesAllowed) and CDI to inject data coming from the token. It is worth mentioning that JAX-RS resource classes must be @RequestScoped (vs @ApplicationScoped) to allow that injection for each request.

A test class named GreetingResourceTest, acting as a REST client, based on RestAssured enables to test the different scenari. It is run with Arquillian for Wildlfly and Liberty. No need of Arquillian with Quarkus.

A few words about MicroProfile JWT

In a nutshell, MicroProfile JWT enables to secure JAX-RS APIs in a scalable and stateless way. The principle is to provide a token for each HTTP request. This token is self-contained: it contains authorization and authentication data as well as a signature to check its integrity and authenticity.

Reminder:

  1. A token is signed by an issuer using its private key
  2. The signature can be checked by third parties using the public key of the issuer.

Anatomy of a JWT Token

A token is made of 3 parts: <header>.<body>.<signature>

The body part is made of claims. A claim is a <key,value> pair. Some claims are standard; custom ones can be defined to transport additional data.

MP JWT introduces 2 specific claims:

  1. upn (User Principal Name): uniquely identifies the subject or user principal of the token. On the server-side, this information can be retrieved as the name property of the Principal and the JsonWebToken
  2. groups: the subject’s group memberships that will be mapped to roles on the server-side. Typically, secured methods in JAX-RS class ressources are annotated with @RolesAllowed.

Using JWTenizr

JWTenizr is an open source library proposed by Adam Bien. It generates a JWT token and a MicroProfile configuration based on 2 input files:

  1. A configuration file named jwtenizr-config.json which defines the key pair (public/private), the token issuer and the location to generate microprofile-config.properties
  2. A template file named jwt-token.json which defines the content of the token body.
jwtenizr-config.json        jwt-token.json
        |                          |
        ––––––––––      ––––––––––––
                 |      |
                 v      v
            ––––––––––––––––––
            |    JWTEnizr    |
            ––––––––––––––––––
                 |      |
           –––––––       –––––––––––––
           |                         |
           v                         v
microprofile-config.properties    token.jwt

In this video Securing JAX-RS Endpoint with JWT, Adam Bien demonstrates how to use it with Quarkus.

A token has a limited lifespan (15 minutes with JWTenizr). To avoid token expiration during tests, JWTenizr is called at the beginning of JUnit tests in order to generate a fresh token.

To that end, JWTenizr is defined in pom.xml as a test dependency. Since it is not available on Maven Central, it must be installed in your local Maven repo:

  1. Download JWTenizr from GitHub
  2. Install it locally by running mvn install.

jwtenizr-config.json

This is the main configuration file:

{
    "mpConfigIssuer": "jefrajames",
    "mpConfigurationFolder": "src/main/resources/META-INF",
    "privateKey": "private key value here",
    "publicKey": "public key value here"
}

Note: for Quarkus and Liberty, mpConfigurationFolder can’t be directly generated in src/main/resources/META-INF.

jwt-token.json

This template file defines the content of the body token in the form of claims:

{"iss":"jefrajames","jti":"42","sub":"jf","upn":"james","groups":["chief","hacker","duke"],"myclaim":"customValue"}

In this example, 4 of them are of specific relevance:

  1. iss: which defines the issuer of the token, this value can optionally be controlled by the endpoint
  2. upn: which defines the User Principal Name
  3. groups: which defines the groups/roles the user belongs to
  4. myclaim: is a custom claim.

Testing with curl

To facilitate the use of curl, each project has a specific curl.sh script that uses the last generated token (from token.jwt) and targets the application specific URL.

When run without argument, curl.sh calls the default hello endpoint. Just add an argument to call other endpoints:

  • curl.sh secured
  • curl.sh forbidden
  • curl.sh myclaim.

Before going to production

Impact on performance

Using MP JWT can impact performance in several ways:

  1. It increases the size of HTTP requests. According to my tests, the size of a token is around 600 bytes
  2. On the server-side, it requires JAX-RS ressource classes to be @RequestScoped (vs @ApplicationScoped): hence these classes are not reusable, a new instance is created per request which adds some overhead
  3. The signature is checked for each request to validate the token.

In most cases, the performance loss is acceptable, but should be kept in mind. Don’t be surprised to measure a slowdown!

Improving security

Each part of a JWT token is Base64 encoded. Being Base64 encoded doesn’t mean that it is cyphered. A “man in the middle” attack enables to steel and reuse it. This risk can be mitigated in two ways:

  1. By limiting the tokens lifespan: a tradeoff must be strike between performance and security. To make it simple: small values increase security (limiting the risk of inappropriate reuse) while high values increase performance (less token generation)
  2. By using HTTPS as transport layer: in this way a ciphered communication channel is established between clients and servers preventing tokens to be stolen and reused.

Needless to say that in production, both are recommended.

Using a Public Key Infrastructure (PKI)

MP JWT is based on RSA algorithms using public/private key pairs. Public key distribution and renewall must be taken into account using a PKI.

Using an Identity & Access Management (IAM)

JWTenizr is a nice tool in devevelopment. Using an IAM such as Keycloak in production is a must.

Here are 2 articles explaining how to configure Wildfly and OpenLiberty with Keycloack:

Conclusion

This project allowed me to see how easy it is to secure a JAX-RS API using MP JWT from a developer perspective. There are some differences between Liberty, Wildfly and Quarkus (all of them are detailed in the README files), mostly in terms of configuration and testing, but the main part of the code remains the same.

Do not hesitate to check it out by yourself in my GitHub project.


Posted

in

by

Comments

Leave a comment