IBM Developer

Tutorial

IBM MQ JWT authentication with JWKS for JMS applications

Configure an IBM MQ queue manager for JWT token authentication and connect a JMS client using JWKS

By Shreeya Nallagonda, Richard J. Coppen

Since version 9.4.0, IBM MQ has supported JSON Web Key Sets (JWKS) for JWT authentication. This feature provides a streamlined and secure way to manage public keys for token validation.

A JSON Web Token (JWT) is a compact, open standard that allows secure transmission of information. These tokens contain encoded information that verifies the identity of a user and determines which resources that user has permission to access.

A JSON Web Key Set (JWKS) provides a standardized approach to manage public keys for JWT token validation.

Important: This tutorial requires access to an x86-64 machine, because other platforms do not currently support JWKS. All host terminal instructions in this tutorial are performed on a Linux machine.

What is JWKS?

JWKS (JSON Web Key Set) is a document that holds a collection of JSON Web Keys, each representing a cryptographic key with unique attributes. These attributes let you know how the key is to be used. Here’s a quick look at some of these key attributes:

  • kty: The key type, such as RSA or EC.
  • kid: Key ID, a unique identifier for each key.
  • use: Key usage, indicating if the key is for signing (sig) or encryption (enc).
  • alg: Cryptographic algorithm associated with the key, like RS256.

Since a JWKS only contains public (or symmetric) keys, there's no risk of private keys being compromised. These public keys are what IBM MQ uses to verify client JWTs by confirming the token issuer and signature.

Why use JWKS over just JWT?

While JWT provides a compact way to transmit data securely, JWKS adds another layer of convenience and security. With JWKS, IBM MQ can dynamically retrieve and validate keys directly from a JWKS endpoint, eliminating the need to handle and store public keys manually. This has several benefits:

  • Centralized key management: JWKS allows for centralized key control, making it easier to rotate keys and ensuring that clients stay updated without needing individual updates.
  • Dynamic key discovery: MQ can pull the latest keys automatically from a JWKS URL.
  • Enhanced security with key ID (kid): The kid attribute ensures that the correct public key is used to validate each JWT.

Therefore, JWKS enables a more scalable, secure, and error-resistant mechanism for public key management compared to a standalone JWT.

How does JWT authentication work with JWKS and IBM MQ?

JWT authentication works as follows:

  1. Deploy a token issuer to obtain JWTs and a JWKS: A token issuer is an entity that creates and signs tokens. It can be an LDAP user registry, a single sign-on service, or an open-source application such as Keycloak. Once this authorization server is set up and accessible, your client and queue manager can retrieve tokens and a JWKS.

  2. Configure IBM MQ to trust and connect to the token issuer: Your MQ queue manager will then need to be configured with the JWKS endpoint to be able to validate your client’s token.

  3. Retrieve the JWKS for token validation: The queue manager retrieves the JWKS from the authorization server using HTTPS to ensure integrity. It uses the JWKS endpoint configured earlier to do so.

  4. The application sends over the JWT: Your messaging client application uses the JWT endpoint to retrieve a token and passes it into the queue manager at connection time.

  5. Queue manager validation: Each JWT header contains a kid that identifies which public key should validate it. IBM MQ’s queue manager compares this kid in the client’s token with the kids provided in the JWKS.

  6. Verifying the signature for authentication: Finally, the queue manager uses the appropriate public key from the JWKS to confirm the JWT’s signature, allowing authenticated clients to access resources in MQ securely.

Architecture for configuring token-based authentication

In this tutorial, you learn how to configure an MQ queue manager to process JWTs using a JWKS and run a JMS client that will communicate with your queue manager using token-based authentication.

The following diagram illustrates the components that you set up in this tutorial. The token issuer and queue manager run in separate containers on your machine, and your JMS client runs as a standalone application.

architecture of token-based authentication using JWTs and a JWKS

Prerequisites

Steps

  • Step 1. Set up a Keycloak server in a container
  • Step 2. Configure your queue manager to accept authentication via JWKS
  • Step 3. Configure and run your JMS client

Step 1. Set up a Keycloak server in a container

You run a Keycloak server in a container.

For JWKS authentication, an HTTPS certificate creates a secure connection between the queue manager and the Keycloak server (token issuer). In production, you use certificates that a certificate authority issues.

In this development sample, you create a local keystore and self-signed certificate to enable HTTPS communication:

  1. Open a terminal and execute the following command to create a Keycloak keystore directory:

     mkdir keycloakKeystore
    
  2. Use OpenSSL to create your private and public keys, and then combine them to create your keystore:

     openssl req -newkey rsa:2048 -nodes -keyout keycloakKeystore/keycloakPrivate.pem -x509 -days 365 -out keycloakKeystore/keycloakPublic.pem -subj "/C=GB/ST=MYSTATE/L=MYCITY/O=MYORG, Inc./OU=IT/CN=<hostname>" -addext "subjectAltName=DNS:<hostname>,IP:127.0.0.1"
    

    Replace hostname with the host name of your host machine. To find out your machine’s host name, run a simple hostname command in your terminal.

  3. Then run the following command to create your keystore.

     openssl pkcs12 -inkey keycloakKeystore/keycloakPrivate.pem -in keycloakKeystore/keycloakPublic.pem -export -out keycloakKeystore/keycloak.p12
    

    Make sure that you set a keystore password when prompted to do so.

  4. Confirm your keystore has been created using this command:

     openssl pkcs12 -in keycloakKeystore/keycloak.p12 -noout -info
    

    You should see output similar to this:

    Output from creating an OpenSSL keystore

  5. Give this keystore the necessary permissions to be read.

     chmod 770 keycloakKeystore && chmod 440 keycloakKeystore/keycloak.p12
    
  6. Start the Keycloak server, passing the keystore directory as a volume by running the following command.

    Remember to edit both passwords in the command. Replace KEYCLOAK_ADMIN_PASSWORD with a password of your choice, replace https-key-store-password with the password that you used to create your Keycloak keystore, and replace path with the path to your Keycloak keystore directory.

     podman run -p 32030:32030 -e KEYCLOAK_ADMIN=kcadmin -e KEYCLOAK_ADMIN_PASSWORD=passw0rd -v /path/to/keycloakKeystore/:/path/to/keycloakKeystore/  quay.io/keycloak/keycloak:latest start --hostname-strict=false --https-key-store-file=/path/to/keycloakKeystore/keycloak.p12 --https-key-store-password=password --https-port=32030
    
  7. Next, use the Keycloak console for the rest of the configuration.

    By default, your Keycloak server issues lightweight tokens, which the queue manager does not accept. To address this issue, change the Keycloak configuration. Open a web browser and go to your JWKS endpoint, https://<hostname>:32030, specifying your host name.

    Keycloak console login page

    Log in using your credentials that you set up when starting your Keycloak server. If you used the same credentials that were used in this tutorial, then these credentials are kcadmin and passw0rd.

    Go to Clients > admin-cli > Advanced > Advanced settings. Then, click on the toggle to turn the lightweight token off and click Save.

    Keycloak Advanced settings, toggle for lightweight access token

  8. Now, create your messaging app user by using the console. Navigate to the Users tab on the left side menu, and click Add user.

    Keycloak Users tab in console

    In the displayed dialog, name your user app so that MQ can authorize your token. Then, click Create.

    Keycloak Add user dialog box

    Navigate to the top panel, and click the Credentials tab.

    Click Set password. Specify a password, turn off Temporary, and then click Save.

    Keycloak console Set password for app dialog

    The JMS client application presents this Keycloak user when it fetches an authentication token from the JWT endpoint.

  9. Now, check that your server works and serves both a JWT and a JWKS with your new Keycloak user credentials.

    Open up a new terminal window and execute the following commands.

    To fetch a JWT (as needed by the client application), run this command:

     curl -k -X POST "https://<hostname>:32030/realms/master/protocol/openid-connect/token" -H "Content-Type: application/x-www-form-urlencoded" -d "username=app" -d "password=passw0rd" -d "grant_type=password" -d "client_id=admin-cli"
    

    Then, to fetch a JWKS response (as needed by the validating queue manager), run this command:

     curl -k -X GET https://<hostname>:32030/realms/master/protocol/openid-connect/certs
    

You now have a token issuer running in a container.

Step 2. Configure your queue manager to accept authentication via JWKS

The first step is to set up a queue manager that runs in a container with an external volume for persistence. If you do not already have one set up, complete the steps in the “Get an IBM MQ queue for development in a container” tutorial.

If you are working on a Windows machine, follow this IBM MQ container setup guide for WSL to set up a customized IBM MQ container on WSL. All host terminal instructions in this tutorial are performed in a Linux subsystem.

Now, if you run podman ps, you should see 2 containers running. One is your queue manager, and the other is the Keycloak server.

Output of podman ps command

Your queue manager has a file called qm.ini. This file contains your queue manager’s configuration and must be edited so the queue manager can find your JWKS endpoint and keystore. Edit the file locally, and then copy it back to your queue manager. You can edit the file either in a terminal or by using an editor of your choice.

For convenience, this file is in the mq-dev-patterns repository. This repository also contains the JMS samples that you run. Run the following command to clone the repository from GitHub.

git clone https://github.com/ibm-messaging/mq-dev-patterns.git

Change to the jwt-jwks-docs directory:

cd mq-dev-patterns/jwt-jwks-docs

If you run an ls command, you should see the qm.ini file listed. Edit this file.

  1. Edit the SSL stanza in the qm.ini file, and add a JWKS stanza at the end. Make sure that you replace the hostname with your host name. When you are done, it should look like this:

    qm.ini file with JWKS stanza

    The JWKS endpoint contains 3 important fields:

    • IssuerName: Refers to URL of your token issuer. It is the ‘iss’ claim in a JWT.
    • Endpoint: This is the URL to access the public keys to verify your JWT’s signature.
    • UserClaim: Declares which field in an incoming token will be taken as the MQ user identity.
  2. Copy this file back into your queue manager.

     podman cp qm.ini QM1:/var/mqm/qmgrs/QM1/
     cd ../..
    
  3. You must also give your queue manager the Keycloak server’s public certificate so that it can trust the token issuer.

     podman cp keycloakKeystore/keycloakPublic.pem QM1:/var/mqm/qmgrs/QM1/ssl/
    
  4. Next, create your queue manager’s keystore, which is where the Keycloak public certificate is imported into.

    To do so, you need command-line access inside the queue manager:

     podman exec -ti QM1 bash
    

    If your container is not called ‘QM1’, change the previous command to use the name of your container.

  5. Run the following command to create your queue manager’s keystore in the default location.

     opt/mqm/bin/runmqakm -keydb -create -db /var/mqm/qmgrs/QM1/ssl/mqdefcer.p12 -pw password -type pkcs12 -stash
    
  6. Now, import the Keycloak’s certificate into your queue manager keystore.

     runmqakm -cert -add -db /var/mqm/qmgrs/QM1/ssl/mqdefcer.p12 -pw password -label keycloakPublicLabel -file /var/mqm/qmgrs/QM1/ssl/keycloakPublic.pem
    
  7. Refresh security in the queue manager so that it can recognize the JWKS endpoint and read the keystore by running these 2 commands:

     runmqsc
    
     REFRESH SECURITY
    
  8. Run exit twice to return to your home directory on your host machine.

Your queue manager can now trust the Keycloak server.

Step 3. Configure and run your JMS client

To connect to the Keycloak server, your JMS application will need a Java keystore holding the Keycloak public certificate to validate the Keycloak server’s HTTPS certificate.

  1. Import the Keycloak public certificate into a Java keystore.

     keytool -importcert -file keycloakKeystore/keycloakPublic.pem -keystore clientkey.jks -alias "jmsClientHttps"
    

    Follow the prompts asking you to enter both passwords for your new Java keystore and your source keystore.

  2. You can check whether the certificate was successfully imported with this command:

     keytool -list -v -keystore clientkey.jks
    
  3. Change directories to the mq-dev-patterns directory, and modify the env.json file to replace the queue manager endpoints and the JWT endpoints with the following code. Be sure to update <hostname> in the JWT_TOKEN_ENDPOINT variable, and specify the JWT_TOKEN_USERNAME and JWT_TOKEN_PWD values for the app user that you set up earlier by using the Keycloak console.

     {
       "MQ_ENDPOINTS": [{
         "HOST": "127.0.0.1",
         "PORT": "1415",
         "CHANNEL": "DEV.APP.SVRCONN",
         "QMGR": "*QM1TLS",
         "APP_USER": "app",
         "APP_PASSWORD": "passw0rd",
         "QUEUE_NAME": "DEV.QUEUE.1",
         "BACKOUT_QUEUE": "DEV.QUEUE.2",
         "MODEL_QUEUE_NAME": "DEV.APP.MODEL.QUEUE",
         "DYNAMIC_QUEUE_PREFIX": "APP.REPLIES.*",
         "TOPIC_NAME": "dev/"
       }],
       "JWT_ISSUER" : {
         "JWT_TOKEN_ENDPOINT":"https://<hostname>:32030/realms/master/protocol/openid-connect/token",
         "JWT_TOKEN_USERNAME":"app",
         "JWT_TOKEN_PWD":"passw0rd",
         "JWT_TOKEN_CLIENTID":"admin-cli"
       }
     }
    
  4. Navigate to the JMS directory, and build the samples with Maven using the following command.

     mvn clean package
    
  5. Run the following command to run the JmsPut sample.

     java -Djavax.net.ssl.trustStore=/path/to/clientkey.jks -Djavax.net.ssl.trustStorePassword=password -cp target/mq-dev-patterns-0.1.0.jar com.ibm.mq.samples.jms.JmsPut
    

Your output should look similar to this, and you should be able to see your client fetching a token (deliberately blurred in the following image) from your Keycloak server.

JMS client app fetching a token from the Keycloak server, with the token blurred

Your client application has successfully connected to your IBM MQ queue manager via JWT-based authentication.

Troubleshooting JWT authentication

Setting up JWT authentication for a queue manager and the MQI client with a token issuer requires configuration on all 3 sides: the Keycloak server, the queue manager, and the JMS client. The details can be easy to miss, which can lead to confusing errors.

Before you review specific cases, see the following documentation to understand the error codes that you might encounter.

For more troubleshooting information, see the MQ Cheat Sheet for Developers for common IBM MQ errors.

A common issue: Are your containers still up and running?

In this tutorial, you set up 2 containers on your host machine: the queue manager and the Keycloak server. If either container stops, you might see a “connection refused” error like the one in the following figure when the Keycloak server is not running.

“connection refused” error

Or, you might see a “failed to connect” error like the one in the following figure, when the queue manager is not running.

“failed to connect” error

To check if the containers are up and running, run a simple podman ps, which will show you all containers that are currently running.

The Access Token not found error

A JSON parsing exception such as the “access token not found” error usually implies that there is an issue with your Keycloak server configuration or that your env.json file has invalid endpoints.

“access token not found” error

First, check if you can fetch a token:

curl -k -X POST "https://<hostname>:32030/realms/master/protocol/openid-connect/token" -H "Content-Type: application/x-www-form-urlencoded" -d "username=app" -d "password=passw0rd" -d "grant_type=password" -d "client_id=admin-cli"

If you see a message that says {"error":"invalid_grant","error_description":"Invalid user credentials"}, this suggests that you either did not create an app user or set it up as a temporary user. Return to the Keycloak web console, and add a new user as described at the end of step 1.

If you already have the app user but did not turn off Temporary, you must delete that user and recreate the app user.

Keycloak Add user dialog

Once the curl command works and you can successfully fetch a token, try running the JMS app again. If the issue persists, check your env.json file for incorrect endpoints/credentials.

The 2035 MQRC unauthorized error

Whenever you see the 2035 error code, it means that something went wrong with the interaction between your client and MQ. To understand the specific issue, inspect the error logs.

The default location for the errors subdirectory is /var/mqm/qmgrs/<queue manager name>/errors on AIX and Linux systems or C:\Program Files\IBM\MQ\qmgrs\<queue manager name>\errors on Windows systems.

If you followed this tutorial and you have a queue manager running in a Linux-based container, you must first attach to the container to get command-line access.

podman exec -ti QM1 bash

Now navigate to the errors subdirectory:

cd /var/mqm/qmgrs/QM1/errors/

If you now run ls, you should see the error logs:

IBM MQ error logs directory listing

The latest logs are written to the AMQERR01.LOG file:

cat AMQERR01.LOG

The 111 error code

A common error code is 111. This code means that your qm.ini file either does not have a JWKS stanza or has one with invalid values.

IBM MQ error code 111 in logs

Ensure that the following stanza is at the bottom of your qm.ini file with the correct IssuerName claim:

stanza in qm.ini file with correct ‘IssuerName’ claim

You can use jwt.io to check your IssuerName. This website allows you to decode tokens, format them clearly, and check associated attributes such as IssuerName.

Use the curl command from earlier to fetch a token. Then, copy the access token section and paste it into the jwt.io website.

JWT access token in jwt.io decoder

You can see all the claims from the token on the right side. The iss claim refers to the IssuerName. Once you correct the IssuerName in the qm.ini file, run the refresh security MQSC command to update the queue manager with the new configuration.

The 107 error code

Another error code that you might see is 107. This code usually means that your token does not have a user claim that the queue manager can adopt for the user ID. This issue tends to happen when your Keycloak server issues lightweight tokens, which results in insufficient information for the queue manager to validate that token.

IBM MQ error code 107 in logs

Go to the Keycloak web console, Clients > admin-cli > Advanced > Advanced settings and switch lightweight token off.

Keycloak web console settings

Summary

Congratulations! In this tutorial, you learned how IBM MQ integrates token authentication by using JWTs and JWKS. You also learned how to configure an IBM MQ queue manager to enable token authentication and then connect a JMS application to the queue manager to put messages onto a queue.

To learn more about how IBM MQ works with authentication tokens, take a look at the IBM MQ authentication tokens documentation.