In this part, you learn to configure the Workday connection on watsonx Orchestrate by setting the identity provider as Entra ID.

Before you begin

Setting the connection between Workday and watsonx Orchestrate

You can configure Workday connection in two ways: The configuration with file is the recommended approach.

Configuring the Workday connection with file

Follow the steps to create and import the file:
  1. Create a configuration file and name it as connections.yaml.
  2. Insert the following code into the connections.yaml file.
    YAML
    spec_version: v1
    kind: connections
    app_id: <workday-app-id>
    environments:
        draft:
        kind: oauth_auth_on_behalf_of_flow
        type: member
        sso: true
        server_url: https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token
        idp_config:
            header:
            content-type: application/x-www-form-urlencoded
            body:
            requested_token_use: on_behalf_of
            requested_token_type: urn:ietf:params:oauth:token-type:saml2
        app_config:
            header:
            content-type: application/x-www-form-urlencoded
    
        live:
        kind: oauth_auth_on_behalf_of_flow
        type: member
        sso: true
        server_url: https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token
        idp_config:
            header:
            content-type: application/x-www-form-urlencoded
            body:
            requested_token_use: on_behalf_of
            requested_token_type: urn:ietf:params:oauth:token-type:saml2
        app_config:
            header:
            content-type: application/x-www-form-urlencoded
    
  3. Replace the following values:
    • <workday-app-id> - The ID of the Workday web page that hosts the embedded chat.
    • {tenant-id} - The tenant ID of the Microsoft Entra.
  4. Run the commands to import the connections.yaml file. See Importing Connection using a Connection file for more information.

Configuring the Workday connection with CLI commands

Follow the steps to set the connection:
  1. Enter the following code in the terminal:
    BASH
    orchestrate connections configure \
     --app-id <workday-app-id> \
     --env <env> \
     --type team \
     --kind oauth_auth_on_behalf_of_flow \
     --server-url https://example.workday.com/ccx/api/v1/tenant1
    
  2. Replace the following values:
    • <workday-app-id> - The ID of the Workday web page that hosts the embedded chat.
    • <env> - The environment type, draft or live.
    • <token-url> - Token URL provided in your Workday API registration.

Configuring the identity provider as Entra ID

  1. Enter the following code in the terminal:
    BASH
    orchestrate connections set-identity-provider \
     --app-id <workday-app-id> \
     --env <env> \
     --url https://login.microsoftonline.com/{tenant-id}/token \ 
     --client-id <entra-client-id> \ 
     --client-secret <entra-client-secret> \ 
     --scope http://www.workday.com/user_impersonation \ 
     --grant-type urn:ietf:params:oauth:grant-type:jwt-bearer \ 
     --requested_token_use=on_behalf_of \ --requested_token_type=urn:ietf:params:oauth:token-type:saml2
    
    Note: If you are using IBM Security Verify as your identity provider, you need to specify only the grant_type. You can remove the requested_token_use and requested_token_type from the code.
  2. Replace the following values:
    • <workday-app-id> - The ID of the Workday web page that hosts the embedded chat.
    • <env> - The environment type, draft or live.
    • {tenant-id} - The tenant ID of the Microsoft Entra.
    • <entra-client-id> - The Client ID of the Microsoft Entra.
    • <entra-client-secret> - The Client Secret of the Microsoft Entra.

Importing tool

You need to import your tool with the Workday application to watsonx Orchestrate. You can use the orchestrate tools import command with the --app-id argument to associate the Workday connection with the tool. See the steps:
  1. Run the command according to the tool file type: Python
    BASH
    orchestrate tools import -k python -f <path-to-file> --app-id <workday-app-id>
    
    YAML (OpenAPI tools)
    BASH
    orchestrate tools import -k openapi -f <path-to-file> --app-id <workday-app-id>
    
  2. Replace the following values:
    • <workday-app-id> - The ID of the Workday web page that hosts the embedded chat.
    • <path-to-file> - The path, name, and extension of the file
For more information, see [Associating a connection to a tool].

Configuring the Workday OAuth credentials

  1. Enter the following code in the terminal:
    BASH
    orchestrate connections set-credentials \ 
    --app-id <workday-app-id> \ 
    --env <env> \ 
    --client-id <client-id-workday> \ 
    --token-url <token-url>  \
    --grant-type authorization_code
    
  2. Replace the following values:
    • <workday-app-id> - The ID of the Workday web page that hosts the embedded chat.
    • <env> - The environment type, draft or live.
    • <client-id-workday> - Client ID provided in your Workday API registration.
    • <token-url> - Token URL provided in your Workday API registration.

Adding watsonx Orchestrate web chat to your application

After the configuration is complete, embed the watsonx Orchestrate web chat to your web page or application. For detailed instructions on embedding the web chat, refer to Enabling Secure Embed Flow. To enable SSO, in addition to the embed instructions, complete the following steps:
  1. Create the createJWT.js and embed.html files. You can use the examples that are provided in the following tabs as templates for each file.
  2. Save these files in your watsonx Orchestrate repository.
JAVASCRIPT
const fs = require('fs');
const RSA = require('node-rsa');
const crypto = require('crypto');
const jwtLib = require('jsonwebtoken');
const express = require('express');
const path = require('path');
const { v4: uuid } = require('uuid');

const router = express.Router();

// If you want to generate your own private/public key pair, you can use commands like the following.
//
// ssh-keygen -t rsa -b 4096 -m PEM -f example-jwtRS256.key
// openssl rsa -in example-jwtRS256.key -pubout -outform PEM -out example-jwtRS256.key.pub

// *** DO NOT USE THE PUBLIC AND PRIVATE KEYS FROM THIS EXAMPLE FOR PRODUCTION USE! ***

// This is your private key that you will keep on your server. This is used to sign the jwt. You will paste your public
// key into the appropriate field on the Security tab of the web chat settings page. IBM watsonx Assistant will use your
// public key to validate the signature on the jwt.
const PRIVATE_KEY = fs.readFileSync(path.join(__dirname, '../keys/<jwt-key>'));

// This is IBM's public key and can be found on the Security tab of the web chat settings page. This is only necessary
// if you wish to encrypt a user payload in your jwt. The code below will use this key to encrypt the user payload
// inside the JWT.
const IBM_PUBLIC_KEY = fs.readFileSync(path.join(__dirname, '../keys/ibmPublic.key.pub'));


// A time period of 45 days in milliseconds.
const TIME_45_DAYS = 1000 * 60 * 60 * 24 * 45;

/**
* Generates a signed JWT. The JWT used here will always be assigned a user ID using the given anonymous user ID. If
* the user is authenticated and we have session info, then info about the user will also be added to the JWT. We
* always use the anonymous user ID even if the user is authenticated because IBM watsonx Assistant doesn't allow you to
* change the user ID in the middle of a session.
*/
function createJWTString(anonymousUserID, sessionInfo) {
  // This is the content of the JWT. You would normally look up the user information from a user profile.
  const jwtContent = {
    // This is the subject of the JWT which will be the ID of the user.
    //
    // This user ID will be available under integrations.channel.private.user.id in dialog and
    // system_integrations.channel.private.user.id in actions.
    sub: anonymousUserID,
    // This object is optional and contains any data you wish to include as part of the JWT. This data will be
    // encrypted using IBM's public key so it will not be visible to your users. IBM watsonx Assistant will decrypt this
    // data and make it available to use in your assistant.
    woUserId: '<wo_user_id>',
    woTenantId: '<wo_tenant_id>',
    user_payload: {
      custom_message: 'This is a secret, encrypted message!!',
      name: 'Username',
      custom_user_id: Username_ID,
      sso_token: <sso_token>,
    },
  };

  // If the user is authenticated, then add the user's real info to the JWT.
  if (sessionInfo) {
    jwtContent.user_payload.name = sessionInfo.userName;
    jwtContent.user_payload.custom_user_id = sessionInfo.customUserID;
  }

  // const dataString = JSON.stringify(jwtContent.user_payload);


  if (jwtContent.user_payload) {
  // Example code snippet to encrypt sensitive data in JWT payload.
    // If there is a user payload, encrypt it using the IBM public key
    // and base64 format.
    const dataString = JSON.stringify(jwtContent.user_payload);
    const utf8Data = Buffer.from(dataString, 'utf-8'); // convert utf-8 to byte-stream
    
    // Encrypt the data
    const rsaKey = new RSA(IBM_PUBLIC_KEY);
    jwtContent.user_payload = rsaKey.encrypt(utf8Data, 'base64');
  }


    // // Encrypt the data
    // const encryptedBuffer = crypto.publicEncrypt(
    //   {
    //     key: IBM_PUBLIC_KEY,
    //     padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
    //     oaepHash: 'sha256'  // Specify OAEP padding with SHA256
    //   },
    //   Buffer.from(dataString, 'utf-8')
    // );

    // // Convert encrypted data to base64
    // jwtContent.user_payload = encryptedBuffer.toString('base64');
    console.log(jwtContent.user_payload)

    // Encrypt the user payload.
    // const rsaKey = new RSA(IBM_PUBLIC_KEY);
    // jwtContent.user_payload = rsaKey.encrypt(jwtContent.user_payload, 'base64');


    // Now sign the jwt content to make the actual jwt. We are giving this a very short expiration time (10 seconds)
    // to demonstrate the web chat capability of fetching a new token when it expires. In a production environment,
    // you would likely want to set this to a much higher value or leave it out entirely.
    const jwtString = jwtLib.sign(jwtContent, PRIVATE_KEY, {
      algorithm: 'RS256',
      expiresIn: '10000000s',
    });

    return jwtString;
  }

  /**
  * Gets or sets the anonymous user ID cookie. This will also ensure that an existing cookie is updated with a new 45
  * day expiration time.
  */
  function getOrSetAnonymousID(request, response) {
    let anonymousID = request.cookies['ANONYMOUS-USER-ID'];
    if (!anonymousID) {
      // If we don't already have an anonymous user ID, then create one. Normally you would want to use a full UUID,
      // but for the sake of this example we are going to shorten it to just five characters to make them easier to read.
      anonymousID = `anon-${uuid().substr(0, 5)}`;
    }

    // Here we set the value of the cookie and give it an expiration date of 45 days. We do this even if we already
    // have an ID to make sure that we update the expiration date to a new 45 days.
    response.cookie('ANONYMOUS-USER-ID', anonymousID, {
      expires: new Date(Date.now() + TIME_45_DAYS),
      httpOnly: true,
    });

    return anonymousID;
  }

  /**
  * Returns the session info for an authenticated user.
  */
  function getSessionInfo(request) {
    // Normally the cookie would contain a session token that we would use to look up the user's info from something
    // like a database. But for the sake of simplicity in this example the session cookie directly contains the user's
    // info.
    const sessionInfo = request.cookies.SESSION_INFO;
    if (sessionInfo) {
      return JSON.parse(sessionInfo);
    }
    return null;
  }

  /**
  * Handles the createJWT request.
  */
  function createJWT(request, response) {
    const anonymousUserID = getOrSetAnonymousID(request, response);
    const sessionInfo = getSessionInfo(request);
    response.send(createJWTString(anonymousUserID, sessionInfo));
  }

  router.get('/', createJWT);

  module.exports = router;
Replace the <wo_tenant_id> and <sso_token> with appropriate information.
ParameterDescription
<wo_tenant_id>Your watsonx Orchestrate tenant ID.
<sso_token>The SSO token obtained from the Entra ID.
Run the following commands to retrieve the SSO token
BASH
curl --location 'https://login.microsoftonline.com/0195ea87-1839-4df0-9739-bf7eec6de925/oauth2/v2.0/token' \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --header 'Cookie: fpc=ArnSgeQ8FmNJkcInGVBWnw94mWYSAQAAAG0uod8OAAAA; stsservicecookie=estsfd; x-ms-gateway-slice=estsfd' \
  --data-urlencode 'grant_type=password' \
  --data-urlencode 'client_id=<client_id>' \
  --data-urlencode 'client_secret=<client_secret>' \
  --data-urlencode 'scope=api://<client_id>/default' \
  --data-urlencode '<username>' \
  --data-urlencode '<password>'

Testing the integration

To test the integration, sign in and interact with Workday through embedded chat.
  1. Log in to the web page or application that hosts the embedded chat using your own credentials.
  2. Start the watsonx Orchestrate chat widget.
  3. Ask a Workday related query in the embedded chat.

Result

If the SSO authentication is correctly configured, the Workday returns a JSON response with the requested data.

Troubleshooting

IssuePossible causeResolution
401 UnauthorizedInvalid SAML assertion or client credentialsCheck your app’s API client credentials
invalid_grantRedirect URI mismatchVerify your IDP app settings
No response in embedded chatChat not embedded correctlyVerify the script and app ID