Secure JAX-RS APIs With Eclipse MicroProfile JSON Web Token
Technical Context and Project Structure
The project uses the following technologies:
- 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 188.8.131.52
- JWTenizr 0.0.4-SNAPSHOT
The project is comprised of three independent modules, each of which is dedicated to a runtime: Quarkus, Wildfly, or Liberty.
To keep it simple and ensure the code and configuration can be flexibly adapted, there is deliberately nothing in common between these modules. As the saying goes, the devil is in the details. Even when using a standard API such as MicroProfile JWT, there are often subtle differences when it comes to configuration, coding, testing, and dependencies. However, I’ve tried to keep the three modules as similar as possible.
Each module is comprised of a JAX-RS resource class, named GreetingResource, that exposes a (very) simple REST API. The API has four methods, each of which returns 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.
A Closer Look at MicroProfile JWT
The MicroProfile JWT programming model is fairly straightforward. It’s based on annotations (@DenyAll, @PermitAll, @RolesAllowed) and uses Contexts and Dependency Injection (CDI) to inject data coming from the token. It’s worth mentioning that JAX-RS resource classes must be @RequestScoped, rather than @ApplicationScoped, to allow that injection for each request.
A test class named GreetingResourceTest, which is based on RestAssured, acts as a REST client to enable testing of the different scenarios. It uses Arquillian for WildlFly and Open Liberty so there’s no need for Arquillian with Quarkus.
MicroProfile JWT allows you to secure JAX-RS APIs in a scalable and stateless way, using a token for each HTTP request. Each token is self-contained: It includes authorization and authentication data as well as a signature to check its integrity and authenticity.
There are two key things to remember:
- A token is signed by an issuer using its private key.
- The signature can be checked by third parties using the issuer’s public key.
Anatomy of a MicroProfile JWT Token
A MicroProfile JWT token is comprised of three parts: <header>.<body>.<signature>
The body is composed of claims. A claim is a <key,value> pair. Some claims are standard, but custom claims can be defined to transport additional data.
MicroProfile JWT introduces two specific claims:
- upn (User Principal Name): Uniquely identifies the subject, or upn, of the token. On the server side, this information can be retrieved as the name property of the Principal and the JsonWebToken.
- groups: The subject’s group memberships that will be mapped to roles on the server side. Typically, secured methods in JAX-RS class resources are annotated with @RolesAllowed.
JWTenizr is an open source library created by Adam Bien. It generates a JWT token and a MicroProfile configuration based on two input files:
- A configuration file, named jwtenizr-config.json, that defines the key pair (public/private), the token issuer, and the location to generate microprofile-config.properties.
- A template file, named jwt-token.json, that defines the content of the token body.
Figure 1 shows the relationships among these files.
Figure 1: JWTenizr Input and Output Files
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 to generate a fresh token. As a result, JWTenizr is defined in pom.xml as a test dependency.
Because JWTenizr is not available in the Maven Central Repository, you need to install it in your local Maven repository by:
- Downloading JWTenizr from GitHub.
jwtenizr-config.json File Contents
The code below show the contents of jwtenizr-config.json, the main configuration file:
"privateKey": "private key value here",
"publicKey": "public key value here"
Note: For Quarkus and Open Liberty, mpConfigurationFolder can’t be directly generated in src/main/resources/META-INF.
jwt-token.json File Contents
The jwt-token.json template file defines the content of the body token in the form of claims, as shown in the example below:
In this example, four claims are particularly relevant:
- iss: Defines the token issuer. This value can optionally be controlled by the endpoint.
- upn: Defines the User Principal Name.
- groups: Defines the groups and roles the user belongs to.
- myclaim: 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 arguments, curl.sh calls the default hello endpoint. Just add an argument to call other endpoints. For example:
- curl.sh secured
- curl.sh forbidden
- curl.sh myclaims
Understanding the Impact on Performance
Using Microprofile JWT can impact performance in multiple ways:
- It increases the size of HTTP requests. According to my tests, the size of a token is around 600 bytes.
- On the server side, it requires JAX-RS resource classes to be @RequestScoped rather than @ApplicationScoped. This means these classes are not reusable and a new instance is created for each request which adds some overhead.
- 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 if your measurements indicate a performance reduction.
Each part of a JWT token is Base64-encoded. This encoding doesn’t mean the token is ciphered. A “man in the middle” attack can steal and reuse it. This risk can be mitigated in two ways:
- By limiting the token lifespan. In this case, a tradeoff must be made between performance and security. Here’s a simple explanation: Small values increase security by limiting the risk of inappropriate reuse, while high values increase performance because fewer tokens are generated.
- By using HTTPS as the transport layer. With this approach, a ciphered communication channel is established between clients and servers, preventing tokens from being stolen and reused.
Needless to say, in production, both mitigations are recommended.
Two additional security measures are also required:
- Using a Public Key Infrastructure (PKI). MicroProfile JWT is based on RSA algorithms that use public/private key pairs. Public key distribution and renewal must be taken into account using a PKI.
- Using Identity and Access Management (IAM). Using an IAM such as Keycloak in production is a must.
Check Out the Project in GitHub
Developing this project allowed me to see how easy it is to secure a JAX-RS API using MicroProfile JWT from a developer perspective.
There are some differences between Open Liberty, WildFly and Quarkus — primarily in terms of configuration and testing — but the majority of the code remains the same. I’ve described these differences in README files that are included in the project.
To learn more, check out the project in GitHub.
Get More Information
For additional insight into the technologies mentioned in this article, see:
- MicroProfile JSON Web Token by Jean-Louis Monteiro from Tomitribe.
- Securing JAX-RS Endpoint with JWT, a video by Adam Bien that demonstrates how to use JWTenizr with Quarkus.
- Using JWT Role Based Access Control with WildFly, published on mastertheboss.com.
- Getting Started to Secure a Simple Java Microservice With Keycloak, MicroProfile and Open Liberty by Thomas Suedbroecker from IBM.