https://github.com/openliberty/guide-microprofile-jwt
An introductory guide on how to control user and role access to microservices with MicroProfile JSON Web Token (MicroProfile JWT): https://openliberty.io/guides/microprofile-jwt.html
https://github.com/openliberty/guide-microprofile-jwt
Last synced: 12 months ago
JSON representation
An introductory guide on how to control user and role access to microservices with MicroProfile JSON Web Token (MicroProfile JWT): https://openliberty.io/guides/microprofile-jwt.html
- Host: GitHub
- URL: https://github.com/openliberty/guide-microprofile-jwt
- Owner: OpenLiberty
- License: other
- Created: 2018-01-15T19:41:20.000Z (over 8 years ago)
- Default Branch: prod
- Last Pushed: 2025-06-02T20:20:06.000Z (about 1 year ago)
- Last Synced: 2025-06-03T10:15:00.376Z (about 1 year ago)
- Language: Java
- Homepage:
- Size: 1.36 MB
- Stars: 14
- Watchers: 6
- Forks: 23
- Open Issues: 4
-
Metadata Files:
- Readme: README.adoc
- Contributing: CONTRIBUTING.md
- License: LICENSE
Awesome Lists containing this project
README
// Copyright (c) 2018, 2025 IBM Corporation and others.
// Licensed under Creative Commons Attribution-NoDerivatives
// 4.0 International (CC BY-ND 4.0)
// https://creativecommons.org/licenses/by-nd/4.0/
//
// Contributors:
// IBM Corporation
:projectid: microprofile-jwt
:page-layout: guide-multipane
:page-duration: 25 minutes
:page-releasedate: 2018-03-09
:page-guide-category: microprofile
:page-essential: false
:page-description: Learn how to control user and role access to microservices by using MicroProfile JWT.
:page-tags: ["microprofile", "security"]
:page-permalink: /guides/{projectid}
:imagesdir: /img/guide/{projectid}
:page-related-guides: ['social-media-login', 'rest-intro', 'cdi-intro']
:common-includes: https://raw.githubusercontent.com/OpenLiberty/guides-common/prod
:source-highlighter: prettify
:page-seo-title: Securing Java microservices with Eclipse MicroProfile JSON Web Token (MicroProfile JWT)
:page-seo-description: A getting started tutorial and an example on how to secure Java microservices to authenticate users and authorize access by validating JSON Web Tokens (JWT) using Eclipse MicroProfile JWT.
:guide-author: Open Liberty
= Securing microservices with JSON Web Tokens
[.hidden]
NOTE: This repository contains the guide documentation source. To view the guide in published form, view it on the https://openliberty.io/guides/{projectid}.html[Open Liberty website^].
You'll explore how to control user and role access to microservices with MicroProfile JSON Web Token (MicroProfile JWT).
// =================================================================================================
// What you'll learn
// =================================================================================================
== What you'll learn
You will add token-based authentication mechanisms to authenticate, authorize, and verify users by implementing MicroProfile JWT in the `system` microservice.
A JSON Web Token (JWT) is a self-contained token that is designed to securely transmit information as a JSON object. The information in this JSON object is digitally signed and can be trusted and verified by the recipient.
For microservices, a token-based authentication mechanism offers a lightweight way for security controls and security tokens to propagate user identities across different services. JSON Web Token is becoming the most common token format because it follows well-defined and known standards.
MicroProfile JWT standards define the required format of JWT for authentication and authorization. The standards also map JWT claims to various Jakarta EE container APIs and make the set of claims available through getter methods.
In this guide, the application uses JWTs to authenticate a user, allowing them to make authorized requests to a secure backend service.
You will be working with two services, a `frontend` service and a secure `system` backend service. The `frontend` service logs a user in, builds a JWT, and makes authorized requests to the secure `system` service for JVM system properties. The following diagram depicts the application that is used in this guide:
image::JWT_Diagram.png[JWT frontend and system services, width=490, height=654, align="center"]
The user signs in to the `frontend` service with a username and a password, at which point a JWT is created. The `frontend` service then makes requests, with the JWT included, to the `system` backend service. The secure `system` service verifies the JWT to ensure that the request came from the authorized `frontend` service. After the JWT is validated, the information in the claims, such as the user's role, can be trusted and used to determine which system properties the user has access to.
To learn more about JSON Web Tokens, check out the https://jwt.io/introduction/[jwt.io website^]. If you want to learn more about how JWTs can be used for user authentication and authorization, check out the Open Liberty https://openliberty.io/docs/latest/single-sign-on.html[Single Sign-on documentation^].
// =================================================================================================
// Getting started
// =================================================================================================
[role='command']
include::{common-includes}/gitclone.adoc[]
=== Try what you'll build
The `finish` directory contains the finished JWT security implementation for the services in the application. Try the finished application before you build your own.
To try out the application, run the following commands to navigate to the `finish` directory and deploy the `frontend` service to Open Liberty:
include::{common-includes}/os-tabs.adoc[]
[.tab_content.windows_section]
--
[role='command']
```
cd finish
mvnw.cmd -pl frontend liberty:run
```
--
[.tab_content.mac_section]
--
[role='command']
```
cd finish
./mvnw -pl frontend liberty:run
```
--
[.tab_content.linux_section]
--
[role='command']
```
cd finish
./mvnw -pl frontend liberty:run
```
--
Open another command-line session and run the following commands to navigate to the `finish` directory and deploy the `system` service to Open Liberty:
include::{common-includes}/os-tabs.adoc[]
[.tab_content.windows_section]
--
[role='command']
```
cd finish
mvnw.cmd -pl system liberty:run
```
--
[.tab_content.mac_section]
--
[role='command']
```
cd finish
./mvnw -pl system liberty:run
```
--
[.tab_content.linux_section]
--
[role='command']
```
cd finish
./mvnw -pl system liberty:run
```
--
After you see the following message in both command-line sessions, both of your services are ready:
[source, role="no_copy"]
----
The defaultServer server is ready to run a smarter planet.
----
// static guide instructions:
ifndef::cloud-hosted[]
In your browser, go to the front-end web application endpoint at http://localhost:9090/login.jsf[http://localhost:9090/login.jsf^]. From here, you can log in to the application with the form-based login.
endif::[]
// cloud-hosted guide instructions:
ifdef::cloud-hosted[]
To launch the front-end web application, click the following button. From here, you can log in to the application with the form-based login.
::startApplication{port="9090" display="external" name="Launch Application" route="/login.jsf"}
endif::[]
Log in with one of the following usernames and its corresponding password:
[cols="<35, ^200, ^200"]
|===
| *Username* | *Password* | *Role*
| bob | bobpwd | admin, user
| alice | alicepwd | user
| carl | carlpwd | user
|===
You're redirected to a page that displays information that the front end requested from the `system` service, such as the system username. If you log in as an `admin`, you can also see the current OS. Click `Log Out` and log in as a `user`. You'll see the message `You are not authorized to access this system property` because the `user` role doesn't have sufficient privileges to view current OS information.
Additionally, the `groups` claim of the JWT is read by the `system` service and requested by the front end to be displayed.
// static guide instructions:
ifndef::cloud-hosted[]
You can try accessing these services without a JWT by going to the https://localhost:8443/system/properties/os[https://localhost:8443/system/properties/os^] `system` endpoint in your browser. You get a blank screen and aren't given access because you didn't supply a valid JWT with the request. The following error also appears in the command-line session of the `system` service:
endif::[]
// cloud-hosted guide instructions:
ifdef::cloud-hosted[]
You can try accessing these services without a JWT by going to the ***system*** endpoint. Open another command-line session by selecting **Terminal** > **New Terminal** from the menu of the IDE. Run the following curl command from the terminal in the IDE:
```bash
curl -k https://localhost:8443/system/properties/os
```
The response is empty because you don't have access. Access is granted if a valid JWT is sent with the request. The following error also appears in the command-line session of the ***system*** service:
endif::[]
[source, role="no_copy"]
----
[ERROR] CWWKS5522E: The MicroProfile JWT feature cannot perform authentication because a MicroProfile JWT cannot be found in the request.
----
When you are done with the application, stop both the `frontend` and `system` services by pressing `CTRL+C` in the command-line sessions where you ran them. Alternatively, you can run the following goals from the `finish` directory in another command-line session:
include::{common-includes}/os-tabs.adoc[]
[.tab_content.windows_section]
--
[role='command']
```
mvnw.cmd -pl system liberty:stop
mvnw.cmd -pl frontend liberty:stop
```
--
[.tab_content.mac_section]
--
[role='command']
```
./mvnw -pl system liberty:stop
./mvnw -pl frontend liberty:stop
```
--
[.tab_content.linux_section]
--
[role='command']
```
./mvnw -pl system liberty:stop
./mvnw -pl frontend liberty:stop
```
--
// =================================================================================================
// Creating the secure system service
// =================================================================================================
== Creating the secure system service
// static guide instructions:
ifndef::cloud-hosted[]
Navigate to the `start` directory to begin.
endif::[]
// cloud-hosted guide instructions:
ifdef::cloud-hosted[]
To begin, run the following command to navigate to the ***start*** directory:
```bash
cd /home/project/guide-microprofile-jwt/start
```
endif::[]
When you run Open Liberty in https://openliberty.io/docs/latest/development-mode.html[dev mode^], dev mode listens for file changes and automatically recompiles and deploys your updates whenever you save a new change. Run the following command to start the `frontend` service in dev mode:
include::{common-includes}/os-tabs.adoc[]
[.tab_content.windows_section]
--
[role='command']
```
mvnw.cmd -pl frontend liberty:dev
```
--
[.tab_content.mac_section]
--
[role='command']
```
./mvnw -pl frontend liberty:dev
```
--
[.tab_content.linux_section]
--
[role='command']
```
./mvnw -pl frontend liberty:dev
```
--
Open another command-line session and run the following command to start the `system` service in dev mode:
include::{common-includes}/os-tabs.adoc[]
[.tab_content.windows_section]
--
[role='command']
```
mvnw.cmd -pl system liberty:dev
```
--
[.tab_content.mac_section]
--
[role='command']
```
./mvnw -pl system liberty:dev
```
--
[.tab_content.linux_section]
--
[role='command']
```
./mvnw -pl system liberty:dev
```
--
After you see the following message, your Liberty instance is ready in dev mode:
[source, role="no_copy"]
----
**************************************************************
* Liberty is running in dev mode.
----
The `system` service provides endpoints for the `frontend` service to use to request system properties. This service is secure and requires a valid JWT to be included in requests that are made to it. The claims in the JWT are used to determine what properties the user has access to.
Create the secure `system` service.
[role="code_command hotspot file=0", subs="quotes"]
----
#Create the `SystemResource` class.#
`system/src/main/java/io/openliberty/guides/system/SystemResource.java`
----
// File 0
SystemResource.java
[source, java, linenums, role='code_column hide_tags=copyright']
----
include::finish/system/src/main/java/io/openliberty/guides/system/SystemResource.java[]
----
This class has role-based access control. The role names that are used in the [hotspot=rolesAllowedAdminUser1 hotspot=rolesAllowedAdminUser2 hotspot=rolesAllowedAdmin file=0]`@RolesAllowed` annotations are mapped to group names in the `groups` claim of the JWT, which results in an authorization decision wherever the security constraint is applied.
The [hotspot=usernameEndpoint file=0]`/username` endpoint returns the system's username and is annotated with the [hotspot=rolesAllowedAdminUser1 file=0]`@RolesAllowed({"admin, "user"})` annotation. Only authenticated users with the role of `admin` or `user` can access this endpoint.
The [hotspot=osEndpoint file=0]`/os` endpoint returns the system's current OS. Here, the [hotspot=rolesAllowedAdmin file=0]`@RolesAllowed` annotation is limited to `admin`, meaning that only authenticated users with the role of `admin` are able to access the endpoint.
While the [hotspot=rolesAllowedAdminUser1 hotspot=rolesAllowedAdminUser2 hotspot=rolesAllowedAdmin file=0]`@RolesAllowed` annotation automatically reads from the `groups` claim of the JWT to make an authorization decision, you can also manually access the claims of the JWT by using the [hotspot=claim file=0]`@Claim` annotation. In this case, the `groups` claim is injected into the [hotspot=rolesArray file=0]`roles` JSON array. The roles that are parsed from the `groups` claim of the JWT are then exposed back to the front end at the [hotspot=rolesEndpoint file=0]`/jwtroles` endpoint. To read more about different claims and ways to access them, check out the https://github.com/eclipse/microprofile-jwt-auth/blob/master/spec/src/main/asciidoc/interoperability.asciidoc[MicroProfile JWT documentation^].
// =================================================================================================
// Creating a client to access the secure system service
// =================================================================================================
== Creating a client to access the secure system service
Create a RESTful client interface for the `frontend` service.
[role="code_command hotspot file=0", subs="quotes"]
----
#Create the `SystemClient` class.#
`frontend/src/main/java/io/openliberty/guides/frontend/client/SystemClient.java`
----
// File 0
SystemClient.java
[source, java, linenums, role='code_column hide_tags=copyright']
----
include::finish/frontend/src/main/java/io/openliberty/guides/frontend/client/SystemClient.java[]
----
This interface declares methods for accessing each of the endpoints that were
previously set up in the `system` service.
The MicroProfile Rest Client feature automatically builds and generates a client implementation based on what is defined in the [hotspot=systemClient file=0]`SystemClient` interface. You don't need to set up the client and connect with the remote service.
As discussed, the `system` service is secured and requests made to it must include a valid JWT in the `Authorization` header. The [hotspot=headerParam1 hotspot=headerParam2 hotspot=headerParam3 file=0]`@HeaderParam` annotations include the JWT by specifying that the value of the `String authHeader` parameter, which contains the JWT, be used as the value for the `Authorization` header. This header is included in all of the requests that are made to the `system` service through this client.
Create the application bean that the front-end UI uses to request data.
[role="code_command hotspot file=1", subs="quotes"]
----
#Create the `ApplicationBean` class.#
`frontend/src/main/java/io/openliberty/guides/frontend/ApplicationBean.java`
----
// File 1
ApplicationBean.java
[source, java, linenums, role='code_column hide_tags=copyright']
----
include::finish/frontend/src/main/java/io/openliberty/guides/frontend/ApplicationBean.java[]
----
The application bean is used to populate the table in the front end by making requests for data through the [hotspot=restClient file=1]`defaultRestClient`, which is an injected instance of the [hotspot=systemClient file=0]`SystemClient` class that you created. The [hotspot=getOs file=1]`getOs()`, [hotspot=getUsername file=1]`getUsername()`, and [hotspot=getJwtRoles file=1]`getJwtRoles()` methods call their associated methods of the `SystemClient` class with the [hotspot=authHeader1 hotspot=authHeader2 hotspot=authHeader3 file=1]`authHeader` passed in as a parameter. The `authHeader` is a string that consists of the JWT with `Bearer` prefixed to it. The `authHeader` is included in the `Authorization` header of the subsequent requests that are made by the `defaultRestClient` instance.
// File 2
LoginBean.java
[source, java, linenums, role='code_column hide_tags=copyright']
----
include::finish/frontend/src/main/java/io/openliberty/guides/frontend/LoginBean.java[]
----
The JWT for these requests is retrieved from the session attributes with the [hotspot=getJwt file=1]`getJwt()` method. The JWT is stored in the session attributes by the provided [hotspot file=2]`LoginBean` class. When the user logs in to the front end, the [hotspot=doLogin file=2]`doLogin()` method is called and builds the JWT. Then, the [hotspot=setAttribute file=2]`setAttribute()` method stores it as an `HttpSession` attribute. The JWT is built by using the [hotspot=jwtBuilder file=2]`JwtBuilder` APIs in the [hotspot=buildJwt file=2]`buildJwt()` method. You can see that the [hotspot=claim file=2]`claim()` method is being used to set the `groups` and the `aud` claims of the token. The `groups` claim is used to provide the role-based access that you implemented. The `aud` claim is used to specify the audience that the JWT is intended for.
// =================================================================================================
// Configuring MicroProfile JWT
// =================================================================================================
== Configuring MicroProfile JWT
Configure the `mpJwt` feature in the `microprofile-config.properties` file for the `system` service.
[role="code_command hotspot file=0", subs="quotes"]
----
#Create the microprofile-config.properties file.#
`system/src/main/resources/META-INF/microprofile-config.properties`
----
// File 0
microprofile-config.properties
[source, text, linenums, role="code_column hide_tags=propagateHeaders"]
----
include::finish/system/src/main/resources/META-INF/microprofile-config.properties[]
----
The following table breaks down some of the properties:
[cols="<30%,<70%", options="header"]
|===
| *Property* | *Description*
| [hotspot=issuer file=0]`mp.jwt.verify.issuer` | Specifies the expected value of the issuer claim on an incoming JWT. Incoming JWTs with an issuer claim that's different from this expected value aren't considered valid.
| [hotspot=header file=0]`mp.jwt.token.header` | With this property, you can control the HTTP request header, which is expected to contain a JWT. You can either specify Authorization, by default, or the Cookie values.
| [hotspot=cookie file=0]`mp.jwt.token.cookie` | Specifies the name of the cookie, which is expected to contain a JWT token. The default value is Bearer.
| [hotspot=audiences file=0]`mp.jwt.verify.audiences` | With this property, you can create a list of allowable audience (aud) values. At least one of these values must be found in the claim. Previously, this configuration was included in the `server.xml` file.
| [hotspot=location file=0]`mp.jwt.decrypt.key.location` | With this property, you can specify the location of the Key Management key. It is a Private key that is used to decrypt the Content Encryption key, which is then used to decrypt the JWE ciphertext. This private key must correspond to the public key that is used to encrypt the Content Encryption key.
| [hotspot=algorithm file=0]`mp.jwt.verify.publickey.algorithm` | With this property, you can control the Public Key Signature Algorithm that is supported by the MicroProfile JWT endpoint. The default value is RS256. Previously, this configuration was included in the `server.xml` file.
|===
For more information about these and other JWT properties, see the https://openliberty.io/docs/latest/reference/microprofile-config-properties.html#jwt[MicroProfile Config properties for MicroProfile JSON Web Token documentation^].
Next, add the MicroProfile JSON Web Token feature to the Liberty `server.xml` configuration file for the `system` service.
[role="code_command hotspot file=1", subs="quotes"]
----
#Replace the system `server.xml` configuration file.#
`system/src/main/liberty/config/server.xml`
----
// File 1
server.xml
[source, xml, linenums, role='code_column hide_tags=copyright']
----
include::finish/system/src/main/liberty/config/server.xml[]
----
The [hotspot=mpJwt file=1]`mpJwt` feature adds the libraries that are required for MicroProfile JWT implementation.
// =================================================================================================
// Building and running the application
// =================================================================================================
== Building and running the application
Because you are running the `frontend` and `system` services in dev mode, the changes that you made were automatically picked up. You're now ready to check out your application in your browser.
// static guide instructions:
ifndef::cloud-hosted[]
In your browser, go to the front-end web application endpoint at http://localhost:9090/login.jsf. Log in with one of the following usernames and its corresponding password:
endif::[]
// cloud-hosted guide instructions:
ifdef::cloud-hosted[]
To launch the front-end web application, click the following button:
::startApplication{port="9090" display="external" name="Launch Application" route="/login.jsf"}
Log in with one of the following usernames and its corresponding password:
endif::[]
[cols="<35, ^200, ^200"]
|===
| *Username* | *Password* | *Role*
| bob | bobpwd | admin, user
| alice | alicepwd | user
| carl | carlpwd | user
|===
After you log in as an `admin`, you can see the information that's retrieved from the `system` service. Click `Log Out` and log in as a `user`. With successfully implemented role-based access in the application, if you log in as a `user` role, you don't have access to the OS property.
You can also see the value of the `groups` claim in the row with the `Roles:` label. These roles are read from the JWT and sent back to the front end to be displayed.
// static guide instructions:
ifndef::cloud-hosted[]
You can check that the `system` service is secured against unauthenticated requests by going to the https://localhost:8443/system/properties/os[https://localhost:8443/system/properties/os^] `system` endpoint in your browser.
endif::[]
// cloud-hosted guide instructions:
ifdef::cloud-hosted[]
You can check that the ***system*** service is secured against unauthenticated requests by going to the **system** endpoint. Run the following curl command from the terminal in the IDE:
```bash
curl -k https://localhost:8443/system/properties/os
```
You'll see an empty response because you didn't authenticate with a valid JWT.
endif::[]
In the front end, you see your JWT displayed in the row with the `JSON Web Token` label.
To see the specific information that this JWT holds, you can enter it into the token reader on the https://JWT.io[JWT.io website^]. The token reader shows you the header, which contains information about the JWT, as shown in the following example:
[source, role="no_copy"]
----
{
"kid": "NPzyG3ZMzljUwQgbzi44",
"typ": "JWT",
"alg": "RS256"
}
----
The token reader also shows you the payload, which contains the claims information:
[source, role="no_copy"]
----
{
"token_type": "Bearer",
"sub": "bob",
"upn": "bob",
"groups": [ "admin", "user" ],
"iss": "http://openliberty.io",
"exp": 1596723489,
"iat": 1596637089
}
----
You can learn more about these claims in the https://github.com/eclipse/microprofile-jwt-auth/blob/master/spec/src/main/asciidoc/interoperability.asciidoc[MicroProfile JWT documentation^].
// =================================================================================================
// Testing the application
// =================================================================================================
== Testing the application
You can manually check that the `system` service is secure by making requests to each of the endpoints with and without valid JWTs. However, automated tests are a much better approach because they are more reliable and trigger a failure if a breaking change is introduced.
[role="code_command hotspot file=0", subs="quotes"]
----
#Create the `SystemEndpointIT` class.#
`system/src/test/java/it/io/openliberty/guides/system/SystemEndpointIT.java`
----
// File 0
SystemEndpointIT.java
[source, java, linenums, role='code_column hide_tags=copyright']
----
include::finish/system/src/test/java/it/io/openliberty/guides/system/SystemEndpointIT.java[]
----
The [hotspot=os file=0]`testOSEndpoint()`, [hotspot=username file=0]`testUsernameEndpoint()`, and [hotspot=roles file=0]`testRolesEndpoint()` tests test the `/os`, `/username`, and `/roles` endpoints.
Each test makes three requests to its associated endpoint. The first [hotspot=adminRequest1 hotspot=adminRequest2 hotspot=adminRequest3 file=0]`makeRequest()` call has a JWT with the `admin` role. The second [hotspot=userRequest1 hotspot=userRequest2 hotspot=userRequest3 file=0]`makeRequest()` call has a JWT with the `user` role. The third [hotspot=nojwtRequest1 hotspot=nojwtRequest2 hotspot=nojwtRequest3 file=0]`makeRequest()` call has no JWT at all. The responses to these requests are checked based on the role-based access rules for the endpoints. The `admin` requests should be successful on all endpoints. The `user` requests should be denied by the `/os` endpoint but successfully access the `/username` and `/jwtroles` endpoints. The requests that don't include a JWT should be denied access to all endpoints.
=== Running the tests
Because you started Open Liberty in dev mode, press the `enter/return` key from the command-line session of the `system` service to run the tests. You see the following output:
[source, role="no_copy"]
----
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running it.io.openliberty.guides.system.SystemEndpointIT
[ERROR ] CWWKS5522E: The MicroProfile JWT feature cannot perform authentication because a MicroProfile JWT cannot be found in the request.
[ERROR ] CWWKS5522E: The MicroProfile JWT feature cannot perform authentication because a MicroProfile JWT cannot be found in the request.
[ERROR ] CWWKS5522E: The MicroProfile JWT feature cannot perform authentication because a MicroProfile JWT cannot be found in the request.
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.648 s - in it.io.openliberty.guides.system.SystemEndpointIT
Results:
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0
----
The three errors in the output are expected and result from the `system` service successfully rejecting the requests that didn't include a JWT.
When you are finished testing the application, stop both the `frontend` and `system` services by pressing `CTRL+C` in the command-line sessions where you ran them.
// =================================================================================================
// Great work! You're done!
// =================================================================================================
== Great work! You're done!
You learned how to use MicroProfile JWT to validate JWTs, authenticate and authorize users to secure your microservices in Open Liberty.
include::{common-includes}/attribution.adoc[subs="attributes"]