Tutorial: Authenticating a user in the middle of a session
If you have web chat security enabled, you must set a user ID for the customer at the beginning of the session as part of the JSON Web Token (JWT) you use to sign messages. For users who are not authenticated, this is typically a generated ID, and it cannot be changed after the JWT is created. However, you can use a private variable to authenticate a user later in the session.
With web chat security enabled, the user ID associated with each message is based on the sub
claim in the JWT payload. This value must be set at the beginning of the session, when the JWT is created, and cannot be changed during the
life of the session. For unauthenticated (anonymous) users, you would typically use a generated ID, saved to a cookie, to ensure that each unique customer is counted only once for billing purposes.
However, you might want your customers to be able to authenticate in the middle of a session (for example, to complete an action that updates the user's account information). Because the generated user ID in the sub
claim cannot be
changed, you need another way to authenticate the user securely. You can do this by storing the customer's actual authenticated user ID as a private variable in the user payload of the JWT. (You could store the user ID in an ordinary context
variable, but this would not be secure, because such variables can be modified.)
For a complete, working version of the example described in this tutorial, see Enabling security for watsonx Assistant web chat (complex).
For a web chat version with minimal codes to enable security, which does not have encrypted payloads and user changes during the session, see Enabling security for watsonx Assistant web chat (simple).
This example in this tutorial, which is based on an Express server for Node.js, shows how to start a session with an anonymous user ID and then authenticate the user during the session.
-
Create a function called
getOrSetAnonymousID()
that generates a unique anonymous user ID for each customer and stores it in a cookie (or, if the cookie already exists, uses the stored user ID).Use a cookie that lasts for at least 45 days. If you do not store the user ID for more than 30 days, the same customer might be counted as multiple different users during the same billing period. (This can still happen if the same user deletes the cookie or uses a different browser.)
function getOrSetAnonymousID(request, response) {
let anonymousID = request.cookies['ANONYMOUS-USER-ID'];
if (!anonymousID) {
anonymousID = `anon-${uuid()}`;
}
response.cookie('ANONYMOUS-USER-ID', anonymousID, {
expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 45), // 45 days.
httpOnly: true,
});
return anonymousID;
}
-
In the function you use to create a JWT, use the anonymous ID returned from the
getOrSetAnonymousID()
function as the value of thesub
claim. This sets the value of the user ID that will be used to uniquely identify the customer for billing purposes.In addition, retrieve any value from the
SESSION_INFO
cookie, which we will use to store authenticated login information. If a value exists, store it in theuser_payload
private claim of the JWT. (If the user has not yet authenticated, this cookie does not yet exist.)
const jwtContent = {
sub: anonymousUserID,
user_payload: {
name: 'Anonymous',
custom_user_id: anonymousUserID,
},
};
if (sessionInfo) {
jwtContent.user_payload.name = sessionInfo.userName;
jwtContent.user_payload.custom_user_id = sessionInfo.customUserID;
}
- Create a function to handle user authentication. In our example, we are using a simple
authenticate()
function that sets a hardcoded user ID, but in a real application the user ID would probably be retrieved from a database after a secure authentication check. Store the user information in theSESSION_INFO
cookie.
function authenticate(request, response) {
const userInfo = {
userName: 'Cade',
customUserID: 'cade-id',
};
response.cookie('SESSION_INFO', JSON.stringify(userInfo), { encode: String });
response.send('Ok');
}
-
When a user logs in, call the
authenticate()
function to store the user information in theSESSION_INFO
cookie. Then call thecreateJWT()
function to regenerate the JWT, using the updated session info to populate theuser_payload
claim.In our example, authentication is simulated with a simple button click. The same button also simulates logging out by deleting the cookie:
async function onClick() { if (getCookieValue('SESSION_INFO')) { document.cookie = 'SESSION_INFO=; Max-Age=0'; } else { await fetch('http://localhost:3001/authenticate'); } const result = await fetch('http://localhost:3001/createJWT'); const newToken = await result.text(); webChatInstance.updateIdentityToken(newToken); updateUI(); }
The anonymous ID in the
sub
claim will continue to be used to track the customer for billing purposes, but now the customer's real user ID is stored separately in the user payload. -
In your actions, you can now access the customer's real user ID by referencing the
user_payload
private context variable:
${system_integrations.chat.private.user_payload}.custom_user_id
For complete working code, see the Enabling security for watsonx Assistant web chat example.
If you are required to comply with GDPR requirements, you might need to persistently store any generated anonymous user IDs, especially for anonymous users who later log in with user credentials. Storing these user IDs makes it possible for you to later delete all data associated with an individual customer if requested to do so.