Using IBM Cloud AppID with WebSphere Liberty OpenID Connect Client

Recently I needed to create a POC setup for a client where our software would run in a k8s cluster in IBM Cloud, and the authentication and authorization would be handled by some external user registry.

Since we're in the IBM Cloud ecosystem, I thought of App ID—it's an IBM service, and they also provide a very handy sample for WAS Liberty! Almost too good to be true!

It was too good to be true.

The way that OIDC connect client is set up (at least for us!) along with JAX-RS (the underlying framework for our REST service) is that it takes group information from the JWT and uses annotations @RolesAllowed to figure out what is allowed and what is not. App ID, on the other hand, puts this information in the scopes claim of the JWT... I played around with it, I asked on forums, I googled the hell out of it, I visited every place I could on the App ID's service on IBM Cloud, and I failed: I couldn't find any way to put the information I wanted in the token.

So I use Azure Active Directory—that setup took 10 minutes from start to finish to set up*.

After that setup was done, I finally stumbled upon a part of the docs for App ID I missed last time around: Customizing tokens. It turns out you can customize the tokens all you want, but you need to do this via the ibmcloud CLI!

The final setup is more or less like this.

  1. Create an IBM Cloud API key for use with the CLI

    API_KEY=$(ibmcloud iam api-key-create APPID)
  2. Obtain an IAM access token using the API key

    IAM_TOKEN=$(curl -k -X POST "https://iam.cloud.ibm.com/identity/token" \
    --header "Content-Type: application/x-www-form-urlencoded" \
    --header "Accept: application/json" \
    --data-urlencode "grant_type=urn:ibm:params:oauth:grant-type:apikey" \
    --data-urlencode "apikey=$API_KEY")
  3. Obtain your Tenant ID (e.g. from the Applications panel of App ID in IBM Cloud), store it as TENANT_ID environment variable.

  4. Obtain the region of your App ID instance abd store it as REGION enviroment variable.

  5. Use curl to update the token configuration

    curl -X PUT "https://$REGION.appid.cloud.ibm.com/management/v4/$TENANT_ID/config/tokens" -H 'Content-Type: application/json' -H "Authorization: Bearer $IAM_TOKEN" -d '
    {
      "access": {
        "expires_in": 3600
      },
      "refresh": {
        "enabled": true,
        "expires_in": 2592001
      },
      "anonymousAccess": {
        "enabled": false
      },
      "accessTokenClaims": [
        {
          "source": "roles",
          "destinationClaim": "groupIds"
        }
      ],
      "idTokenClaims": [
        {
          "source": "roles",
          "destinationClaim": "groupIds"
        }
      ]
    }'
        

Combine that with the Open ID Connect Client configuration on the WAS Liberty side:

<openidConnectClient 
    id="appID"
    clientId="${env.APP_ID_CLIENT_ID}"
    clientSecret="${env.APP_ID_CLIENT_SECRET}"
    authorizationEndpointUrl="https://${env.APP_ID_REGION}.appid.cloud.ibm.com/oauth/v4/${env.APP_ID_TENANT_ID}/authorization"
    tokenEndpointUrl="https://${env.APP_ID_REGION}.appid.cloud.ibm.com/oauth/v4/${env.APP_ID_TENANT_ID}/token"
    issuerIdentifier="https://${env.APP_ID_REGION}.appid.cloud.ibm.com/oauth/v4/${env.APP_ID_TENANT_ID}"
    jwkEndpointUrl="https://${env.APP_ID_REGION}.appid.cloud.ibm.com/oauth/v4/${env.APP_ID_TENANT_ID}/publickeys"
    userInfoEndpointUrl="https://${env.APP_ID_REGION}.appid.cloud.ibm.com/oauth/v4/${env.APP_ID_TENANT_ID}/userinfo"
    userInfoEndpointEnabled="true"
    groupIdentifier="groupIds"
    realmName="appIDrealm"
    authFilterRef="appIdAuthFilter"
    signatureAlgorithm="RS256"
/>

And you have a working setup!

Now you can assign access to your application:

<security-role name="reader">
    <group name="appid-reader" access-id="group:appIDrealm/${APP_ID_READER_GROUP_NAME}" />
</security-role>

* Not counting the bug I discovered in WAS Liberty that prevented auto-discovery feature to work.