Documentation for the classic Watson Assistant experience has moved. For the most up-to-date version, see Overview: Securing a web chat.
Securing the web chat
Understand what you need to do to secure your web chat integration.
Configure the web chat to authenticate users and send private data from your embedded web chat.
All messages that are sent from the web chat are encrypted. When you enable security, your assistant takes an additional step to verify that messages originate from the web chat that is embedded in your website only.
The web chat uses an RSA signature with SHA-256 (RS256) to encrypt communication. RS256 signatures use a sophisticated type of RSA encryption. An RSA key pair includes a private and a public key. The RSA private key is used to generate digital signatures, and the RSA public key is used to verify digital signatures. The complexity of the RSA algorithm that is used to scramble the message makes it nearly impossible to unscramble the message without the key.
The following diagram illustrates the requests that are sent back and forth to authenticate a request.
You can implement the following security measures:
- Ensure that messages sent from the web chat to your assistant come from your customers only
- Send private data from the web chat to your assistant
Before you begin
The process you use to add the web chat to your website is simple. Its simplicity also means it can be misused. That's why it's important to verify that the messages sent to your assistant are coming from authorized users only.
Before you enable security, complete the following steps:
-
Create a RS256 private/public key pair.
You can use a tool such as the OpenSSL command line or PuTTYgen.
- For example, to create the key pair:
openssl genrsa -out key.pem 2048
- For example, to create the key pair:
-
Use your private key to sign a JSON Web Token (JWT). You will pass the token with the messages that are sent from your website as proof of their origin.
The JWT payload must specify values for the following claims:
-
iss
: Represents the issuer of the JWT. This value is a case-sensitive string. -
sub
: Represents the principal that is the subject of the JWT. This value must either be scoped to be locally unique in the context of the issuer or be globally unique. The value you specify forsub
is used as theuser_id
.The user ID that is specified in the
sub
claim is also sent in thecustomer_id
section of theX-Watson-Metadata
HTTP header. Thecustomer_id
can be used to make requests to delete user data. Because the ID is sent in a header field, the syntax must meet the requirements for header fields as defined in RFC 7230 (all visible ASCII characters). For more information about deleting user data, see Labeling and deleting data. -
exp
: Represents the expiration time on or after which the JWT cannot be accepted for processing. Many libraries set this value for you automatically. Set a short-livedexp
claim with whatever library you use.
For more information about JSON Web Tokens, see the RFC7519 and OpenID Connect 1.0 specifications.
Most programming languages offer JWT libraries that you can use to generate a token. The following NodeJS code sample illustrates how to generate a JWT token.
// Sample NodeJS code on your server. const jwt = require('jsonwebtoken'); /** * Returns a signed JWT generated by RS256 algorithm. */ function mockLogin() { const payload = { /* * Even if this is an unauthenticated user, add a userID in the sub claim that can be used * for billing purposes. * This ID will help us keep track "unique users". For unauthenticated users, drop a * cookie in the browser so you can make sure the user is counted uniquely across visits. */ sub: 'some-user-id', // Required iss: 'yourdomain.com' // Required }; // The "expiresIn" option adds an "exp" claim to the payload. return jwt.sign(payload, process.env.YOUR_PRIVATE_RSA_KEY, { algorithm: 'RS256', expiresIn: '10000ms' }); }
-
Enable security
To enable security, complete the following steps:
-
From the Security tab of the web chat integration setup page in Watson Assistant, set the Secure your web chat switch to On.
-
Add your public key to the Your public key field.
The public key that you add is used to verify that data that claims to come from your web chat instance is coming from your web chat instance.
-
To prove that a message is coming from your website, each message that is submitted from your web chat implementation must include the JSON Web Token (JWT) that you created earlier.
Add the token to the web chat code snippet that you embed in your website page. Specify the token in the
identityToken
property.Starting with web chat version 3.2.0, this step is optional. You do not have to add the
identityToken
property initially. You can skip this step as long as you perform the next step where you add theidentityTokenExpired
event. The event is fired when the web chat is first opened, and if a token wasn't provided, it obtains one from your handler at that time.For example:
<script> window.watsonAssistantChatOptions = { integrationID: 'YOUR_INTEGRATION_ID', region: 'YOUR_REGION', serviceInstanceID: 'YOUR_SERVICE_INSTANCE', identityToken: 'YOUR_JWT', onLoad: function(instance) { instance.render(); } }; setTimeout(function(){ const t=document.createElement('script'); t.src="https://web-chat.global.assistant.watson.appdomain.cloud/loadWatsonAssistantChat.js"; document.head.appendChild(t); }); </script>
The JSON Web Token is automatically included on each subsequent request that is sent from the web chat until it expires.
-
You can add an event that is triggered when your token expires, or (starting with web chat version 3.2.0) when no token is specified initially. The event has a callback you can use to update the token and to process any messages that were queued for processing during the time the token was expired.
For example:
<script> window.watsonAssistantChatOptions = { integrationID: 'YOUR_INTEGRATION_ID', region: 'YOUR_REGION', serviceInstanceID: 'YOUR_SERVICE_INSTANCE', identityToken: 'YOUR_JWT', onLoad: function(instance) { instance.on({ type: 'identityTokenExpired', handler: function(event) { // Perform whatever actions you need to take on your system to get a new token. return new Promise(function(resolve, reject) { // And then pass the new JWT into the callback and the service will resume processing messages. event.identityToken = 'YOUR NEW JWT'; resolve(); }); }}); instance.render(); } }; setTimeout(function(){ const t=document.createElement('script'); t.src="https://web-chat.global.assistant.watson.appdomain.cloud/loadWatsonAssistantChat.js"; document.head.appendChild(t); }); </script>
Passing sensitive data
You can optionally copy the public key that is provided by IBM, and use it to add an additional level of encryption to support passing sensitive data from the web chat.
Use this method to send sensitive information in messages that come from your website, such as a information about a customer's loyalty level, a user ID, or security tokens to use in webhooks that you call from your dialog. Information that is passed to your assistant in this way is stored in a private variable in your assistant. Private variables cannot be seen by customers and are never sent back to the web chat.
For example, you might start a business process for a VIP customer that is different from the process you start for less important customers. You likely do not want non-VIPs to know that they are categorized as such. But you must pass this informataion to your dialog because it changes the route of the conversation. You can pass the customer MVP status as an encrypted variable. This private context variable will be available for use by the dialog, but not by anything else.
-
From the web chat configuration page, copy the public key from the IBM provided public key field.
-
From your website, write a function that signs a JSON Web Token.
For example, the following NodeJS code snippet shows a function that accepts a userID and payload content and sends it to the web chat. If a payload is provided, its content is encrypted and signed with the IBM public key.
// Sample NodeJS code on your server. const jwt = require('jsonwebtoken'); const RSA = require('node-rsa'); const rsaKey = new RSA(process.env.PUBLIC_IBM_RSA_KEY); /** * Returns a signed JWT. Optionally, also adds an encrypted user_payload. * The userPayload is stringified JSON. */ function mockLogin(userID, userPayload) { const payload = { sub: userID, // Required // The exp claim is automatically added by the jsonwebtoken library. // We recommend you set a short lived exp claim // with whatever library you are using. }; if (userPayload) { // If there is a user payload we encrypt it using the IBM public key. // Should be encrypted to base64 format. payload.user_payload = rsaKey.encrypt(userPayload, 'base64'); } const token = jwt.sign(payload, process.env.YOUR_PRIVATE_RSA_KEY, { algorithm: 'RS256', expiresIn: '10000ms' }); return token; }
-
The encrypted user payload is decrypted and then saved to the
context.integrations.chat.private.user_payload
object.For information about how to access the payload data from the dialog, see Web chat: Accessing sensitive data. You might want to access the payload, for example, to get the customer importance information or single sign-on credentials that you can subsequently use to authenticate a webhook.
Authenticating users
To authenticate and specify a unique ID for each customer, add the user ID information to the token.
-
From the web chat configuration page, copy the public key from the IBM provided public key field. You will specify this value as the
PUBLIC_IBM_RSA_KEY
later. -
From your website, write a function that signs a JSON Web Token.
The function must accept a UserID parameter and set the userID as the
sub
claim value.For example, the following NodeJS code snippet set the user's ID to
L12345
.// Sample NodeJS code on your server. const jwt = require('jsonwebtoken'); const RSA = require('node-rsa'); const rsaKey = new RSA(process.env.PUBLIC_IBM_RSA_KEY); /** * Returns a signed JWT. Optionally, adds an encrypted user payload in stringified JSON. */ function mockLogin() { const payload = { sub: 'L12345', // Required iss: 'www.example.com' // Required }; const token = jwt.sign(payload, process.env.YOUR_PRIVATE_RSA_KEY, { algorithm: 'RS256', expiresIn: '10000ms' }); return token; }
After you set the value of the
sub
claim to be the user's userID, you cannot change the claim to another user. The userID that you specify with this method is used for billing purposes and can be used to delete customer data upon request.The userID is stored in the context object as
context.global.system.user_id
.The user ID that is specified in the
sub
claim is also sent in thecustomer_id
section of theX-Watson-Metadata
HTTP header. Thecustomer_id
can be used to make requests to delete user data. Because the ID is sent in a header field, the syntax must meet the requirements for header fields as defined in RFC 7230 (all visible ASCII characters). For more information about deleting user data, see Labeling and deleting data.
If you disable security, then you can use the instance.updateUserID()
method to specify user IDs. For more information, see Adding user identity information.
Logging out
To log out a customer, you must destroy the web chat.
If you reload the page when a customer logs out, you must call the instance.destroySession()
method to remove any reference to the current session from the browser's cookies and storage. If you forget to call this method, information
that is protected by the identityToken
is not at risk, but the web chat will try to connect to the previous session and fail.
If you do not perform a full page reload when a customer logs out, call the instance.destroy()
method. The destroy
method removes the current instance of the web chat that is configured for the current userID from the
DOM and browser memory. Next, call the instance.destroySession()
method.
Updating site security policies
If your website uses a Content Security Policy (CSP), you must update it to grant permission to the web chat.
The following table lists the values to add to your CSP.
Property | Additional values |
---|---|
default-src | 'self' *.watson.appdomain.cloud fonts.gstatic.com 'unsafe-inline' |
connect-src | *.watsonplatform.net *.watson.appdomain.cloud |
The following example shows a complete CSP metadata tag:
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self' *.watson.appdomain.cloud fonts.gstatic.com 'unsafe-inline';connect-src *.watsonplatform.net *.watson.appdomain.cloud" />
Allowing elements
If your CSP uses a nonce to add elements, such as <script>
and <style>
tags, to an allowlist, do not use unsafe-inline
to allow all such elements. Instead, provide a nonce value to the web
chat widget as a configuration option. The web chat will then set the nonce on any of the <script>
and <style>
elements that it generates dynamically.
A CSP that passes a nonce to the web chat widget might look like this:
<meta http-equiv="Content-Security-Policy" content="default-src 'self' *.watson.appdomain.cloud fonts.gstatic.com 'nonce-
You can pass the nonce to the web chat by editing the embed script as follows:
window.watsonAssistantChatOptions = {
integrationID: "YOUR_INTEGRATION_ID",
region: "YOUR_REGION",
serviceInstanceID: "YOUR_SERVICE_INSTANCE",
cspNonce: "<server generated value>",
onLoad: function(instance) {
instance.render();
}
};
Security measures
The web chat integration undergoes tests and scans on a regular basis to find and address potential security issues, such as cross-site scripting (XSS) vulnerabilities.
Be sure to run your own security reviews to see how the web chat fits in with your current website structure and policies. The web chat is hosted on your site and can inherit any vulnerabilities that your site has. Only serve content over HTTPS, use Content Security Policy (CSP), and implement other basic web security precautions.