In a recent project, I wanted to quickly setup a jwt junction to a WebSphere Traditional Server (actually to a HCL Portal Server).

I used a WebSphere Container image, that you can find here : WebSphere container images

That did not go as seamless as I had expected !

I suppose the moral of the story is that hardly anybody is still using WebSphere Traditional, and everybody is using Liberty/OpenLiberty (or a different flavored java application server).

WebSphere

For this setup, I’ll just run a WebSphere container on my local machine using Podman. I’ll setup the default application (aka. Snoop), configure OIDCRP as TAI and import certificates. This is all bundled in the build.

A repository with example files needed to run this setup yourself is here : https://github.com/Bozzie4/isva-websphere-jwt

This includes the ContainerFile , and some jython scripts to configure WebSphere.

OIDCRP TAI properties

The OIDC TAI has a number of properties, and although the documentation lists only some properties as required (See https://www.ibm.com/docs/en/was-nd/9.0.5?topic=party-openid-connect-relying-custom-properties#csec_oidprop__usejwtfromrequest), in reality a lot more properties are necessary to get the TAI to work.

These are the properties (as defined in the source information here https://github.com/Bozzie4/isva-websphere-jwt)

parameter value description
provider_1.useJwtFromRequest required  
provider_1.identifier isvajwt This is a unique id in WebSphere
provider_1.issuerIdentifier https://issuer This is the issuer as defined in the incoming jwt
provider_1.interceptedPathFilter /.* This instructs the OIDC TAI to intercept ALL incoming requests …
provider_1.excludedPathFilter /ibm/.*,/admin/.* … except these
provider_1.filter request-url%=/snoop/ I’m not sure what this filter adds to the first one ….
provider_1.tokenReuse true This indicates that an incoming token can be reused (this will be the case with the webseal junction, the same jwt will be sent as long as it’s still valid
provider_1.signVerifyAlias signer The name of the alias, in both the local truststore and jwks.json
provider_1.audiences ALL_AUDIENCES Required, because of the TAI…
provider_1.setLtpaCookie true Optional, this generates an ltpa cookie, so the jwt is only used on the first request
provider_1.useRealm WAS_DEFAULT Required, otherwise the authentication fails (it obviously can be any WebSphere domain)
provider_1.jwkEndpointUrl https://default.verifyaccess.local/jwks.json This is the jwks endpoint on ISVA, there’s a hotfix for ISVA 10.0.6
provider_1.signatureAlgorithm RS256 The algoritm. Must be RS256 for the jwt junction

You can find the jython script to configure the TAI in the Git repository.

Compared to the OpenLiberty/Liberty features that support jwt (eg. mpJwt or openidconnect), this TAI expect more claims to be available although the OIDC specification clearly defines them as optional.

Create a custom docker image

This copies the signer certificates , and a number of configuration scripts into the container.

FROM icr.io/appcafe/websphere-traditional:latest
COPY --chown=was:root files/*.cer /tmp
COPY --chown=was:root scripts/*.py /work/config/
COPY --chown=was:root config/PASSWORD /tmp/PASSWORD
RUN /work/configure.sh

The DefaultApplication and the WebSphereOIDCRP ear files are part of the container image, under /opt/IBM/WebSphere/AppServer/installableApps

You can put a password in the file config/PASSWORD, so that WebSphere does not generate a random one each time …

Build an image based on the containerfile:

podman build -f ContainerFile -t mywebsphere .

Note the container will copy your local /etc/hosts file. So to make the jwkEndPoint available, you can add an entry to your hosts’ /etc/hosts file, it will be added to the container’s /etc/hosts file.

And now start a new container based on the image:

podman run -d --replace --name was -p 9043:9043 -p 9443:9443 localhost/mywebsphere:latest

You’ll need to see a running container using (podman ps)

CONTAINER ID  IMAGE                         COMMAND               CREATED        STATUS        PORTS                                           NAMES
9b8ab2d4cd5b  localhost/mywebsphere:latest  env JVM_EXTRA_CMD...  6 minutes ago  Up 6 minutes  0.0.0.0:9043->9043/tcp, 0.0.0.0:9443->9443/tcp  was

If you don’t see a running container, you need to prepare an selinux policy (or disable selinux in the container altogether), There’s other blog posts on here that you can find ‘SELinux policy for rootless podman on RHEL’

You can access the running container’s Linux console like this:

podman exec -it was /bin/bash

You can now access the WebSphere console (using https://localhost:9043/ibm/console) , and username wsadmin and the password you defined in the PASSWORD file (in my case, passw0rd).

isc.png

This shows that both pre-configured applications are installed and running.

You can also access the installed DefaultApplication (https://localhost:9443/snoop)

WebSeal configuration

Certificates

I’ve created 2 certificates in my reverse proxy’s pdsrv keystore, and exported their public keys.

pdsrv_certificates.png

These need to be added (or at least the public keys) to the WebSphere’s NodeDefaultTrustStore. (This is already part of the ContainerFile build).

Or better put, WebSphere needs 1 of these 2 signer certificates:

  • if you use the jwks.json endpoint, WebSphere needs the default signer to be able to contact to endpoint using https.
  • if you don’t use the jwks.json endpoint, WebSphere needs the signer key.

Note: It should not be necessary to include the signer key, this is the public key for the key that will be used to sign the jwt. This key should be available through jwks.json (the local app), but for some reason, WebSphere cannot handle a JWK Set document that does not contain a use claim.

Update: There is a hotfix available on v10.0.6 (from IBM Support), to include the use claim in the jwks.json of the jwks local-app in WebSeal.

Enable the jwks local app

So one of the nice things about the jwt junction, is that it comes with a local-app, that exposes the public keys of the pdsrv or junction keystore as a Json Web Key Set. This does not work with the WebSphere OIDCRP TAI at this moment, as mentioned above.

[local-apps]
jwks=jwks.json
cred-viewer=credviewer

NOTE there’s an issue in ISVA v10.0.5 , where the kid does not match the key-label from the jwt junction. There’s a hotfix for this, or you can use v10.0.6.

Create an SSL junction

You can now create an SSL WebSeal junction to point to the new application server.

webseal_ssl_junction.png

However , I’m using a TCP junction, because that’s a lot simpler (with every restart of the WebSphere container, it starts with a different certificate).

webseal_tcp_junction.png

Configure a jwt junction

Specifics here are the sub claim (this is the default claim where WebSphere will get the username from), and a fake audience aud claim (I’m not using it, but without it, WebSphere complains).

This configuration will send the jwt in the Authorization header, as a Bearer token.

[jwt:/snoop]
key-label = signer
claim = attr::AZN_CRED_PRINCIPAL_NAME::sub
claim = text::https://issuer::iss
claim = text::fake_audience::aud
claim = attr::tagvalue_session_index::traceid
include-empty-claims = false
hdr-name = Authorization
hdr-format = Bearer %TOKEN%
lifetime = 800
renewal-window = 15

jwt result

The resulting jwt looks like this (through https://jwt.io).

webseal_jwt_junction_resulting_jwt.png

You can use the key from your jwks local-apps , to also verify the signature. And as always, don’t throw private or sensitive (production) data in a public website (base64 decode the part between the first and second dot . )

{
  "exp": 1695655299,
  "nbf": 1695654369,
  "iat": 1695654499,
  "jti": "5c4e77e6-5bb5-11ee-ba24-000c29b389f8",
  "iss": "https://issuer",
  "aud": "fake_audience",
  "sub": "user1",
  "traceid": "5c4d1036-5bb5-11ee-ba24-000c29b389f8"
}

If you have the DefaultApplication working in the end, you can simply pickup the jwt from the output of /snoop (the incoming headers are shown)

Setup ACLs

The jwks.json endpoint (if you can use it) should be publicly available . So you need to assign an anonymous ACL to it.

Create a user in the local registry

So be able to login to WebSeal, you can use any method of course. But in this case, I’ve created a local user in the local registry using the Policy Administration.

Login to /snoop through WebSeal

So everything is ready on both WebSeal and WebSphere.

Start by accessing the snoop url through the webseal hostname : https://`webseal-hostname`/snoop This shows the login page of WebSeal (only authenticated users can access the /snoop junction).

webseal login

The snoop servlet is configured for Application security, so it’s not accessible for unauthenticated users.

snoop_jwt_user1

The jwt is visible (not in the screenshot) a bit lower on the page, but the important info here is the “User Principal”, which comes from the jwt. This indicates the user is authenticated (and authorized) correctly in WebSphere and in the /snoop application.

Next steps

To read the other claims from the incoming jwt (in this example, for instance the claim traceid), more code is required. You can do this in the application, or my implementing a LoginModule on WebSphere.

Troubleshooting

You can enable trace settings on WebSphere, to see a bit more information in the log files.

*=info: com.ibm.ws.security.*=all

Since the WebSphere container uses HPEL logging, there’s no file to tail, instead you can use the logViewer. You can tail the logs like this:

podman exec -it was /opt/IBM/WebSphere/AppServer/profiles/AppSrv01/bin/logViewer.sh -monitor

Alternatively, go into the container and start the logViewer there.

cd /opt/IBM/WebSphere/AppServer/profiles/AppSrv01/bin
./logViewer.sh -monitor