Oracle by Example brandingSecure Your Oracle Web SDK Chat

section 0Before You Begin

This 45-minute tutorial shows you how to secure Oracle Web SDK chat widgets that you add to your web clients.

This is the second tutorial in a series:

You might want to read the first tutorial to learn the Oracle Web SDK basics before you read this tutorial.

Background

One problem with adding chats to web clients is that, unless you secure access to the Oracle Web channel, web client, and chat session, hackers can access the skill and active conversations. With a simple browser console, they can inspect the REST URIs and get the conversation's user ID and channel ID.

Once a hacker knows the channel ID of an unprotected Oracle Web channel, they can host the chat on a fraudulent website. Hackers can also see the general structure or pattern of the user ID, if there is one. For example, if they see that the person's email address is used for the user ID, they know they can spoof that person by simply passing in the channel ID and the user's email address.

Also, if a potential attacker knows that there's an active session, they know the channel ID, and they can obtain or guess the user ID, then they can also listen in on an unprotected chat session's responses.

For these reasons, you should follow these practices to secure the chats:

  • Enable client authentication for the Oracle Web channel, and use JSON Web Tokens (JWT) to verify and validate the web client with the channel.
  • Host the JWT generator on a server that's different from the web client's host.
  • Unless you are in development or testing, always enforce domain allowlists for the Oracle Web channel and the JWT Token generator to prevent connections from fraudulent web clients.
  • Never use real user names for the user ID. Note that here we are talking about the user ID that uniquely identifies the Web SDK chat session, and not the user ID that authenticates a user.

This tutorial shows how to implement these practices. While we will discuss domain allowlists and user ID generation, the main focus of this tutorial is how to use the JWT-token feature to implement client authentication for your web client's chat.

In this tutorial, you'll learn how to:

  • Configure your Oracle Web channel for client authentication.
  • Use an app that runs on a remote server to generate JWT tokens.
  • Use a generated user ID.
  • Build a web app that gets the generated token and uses it to authenticate and authorize the client app after it establishes connection with the Oracle Web channel. It will also send the token during the chat session when necessary, such as when it connects to the speech server, uploads a file, or replaces an expired token.

Before we start building the web client, let's delve a bit more into domain allowlists, client authentication, and the associated JWT tokens.

  • Domain Allowlist: To prevent access from fraudulent websites, you should control which websites can access the skill by specifying a domain allowlist for the Oracle Web channel. A channel will communicate only with sites that are on it's list. To limit access to a specific set of sites, you provide a comma-deliminated list, such as *.corp.example.com,*.hdr.example.com. An asterisk (*) alone allows access from any domain. You typically set it to * only during development, and then add a more specific allowlist for production.
  • Client Authentication: You can further control access to a skill by requiring that the web client use a JSON Web Token (JWT) that's signed with the channel's secret key when it communicates with the Oracle Web channel.

    The JWT token is derived from the following information, which the channel uses to further validate access:

    • Channel ID.
    • Issued-at time (IAT).
    • Expiration time, which can't exceed the channel's maximum expiration time.
    • User ID, which must be a string that uniquely identifies a chat session.

    The Oracle Web SDK automatically passes the token when needed, and it also invokes the token generator to get a new token when the current one expires.

Note that the Oracle Web SDK sends and receives all chat and voice communication over a Secure Web Service encyrpted channel (WSS/HTTPS) to prevent eavesdropping and tampering. In addition, it uploads files (attachments) through Secure HTTP (HTTPS) connections.

What Do You Need?

  • Oracle Digital Assistant 20.02 or later.
  • A Digital Assistant skill. You can use any of your instance's skills, such as one that you created or a sample skill that was provided with your instance. If you completed the Access a Skill from Your Website tutorial, then you can use the skill from that tutorial.
  • Oracle Web SDK 20.5.1 or later unzipped into a folder of your choice. You can download this SDK from your Digital Assistant instance by opening the side menu and then clicking Downloads.
  • Node and the Node Package Manager. These must be installed on your machine so that you can install the the node modules that are dependents of the sample app that we provide. You can download both from https://www.npmjs.com/get-npm.
  • One of the following supported browsers:
    • Firefox
    • Chrome
    • Safari
    • Edge

section 1Set Up the Oracle Web User Channel

To start, you'll create a Digital Assistant user channel that enables an Oracle Web client to access your skill. To ensure that only your web clients can use this channel, you'll configure it to require client authentication.

  1. In your Oracle Digital Assistant instance, click the hamburger icon hamburger icon to open the side menu, and then click Development > Channels.
  2. From the Channels page, click + channel button to add a channel.
  3. As shown in the following screenshot, enter these values on the Create Channel page:

    Field Value
    Name A unique name that begins with a letter and contains only letters, numbers, periods,and underscores (_). For example: AB_JWT_tutorial.
    Description (Optional) What the channel is used for.
    Channel Type Oracle Web
    Allowed Domains *

    An asterisk alone allows any domain.

    Client Authentication Enabled Switch to On.

    Shows the fields that are described in the table.

  4. Click Create.
  5. In the Route To drop-down list, select the skill that you want to use for this tutorial.
  6. Switch Channel Enabled to On.

  7. Note the secret key and channel ID that are shown for this channel. Later in this tutorial, you'll use these values in your web app and the token generator's routing configuration.
  8. Also note the Oracle Digital Assistant host and domain (which is referred to as the fully-qualified domain name) from the instance's URL that appears in the browers's address field.

    For example: oda-xxxx.data.digitalassistant.oci.oraclecloud.com

    You'll use this value later in the tutorial.

Your instance is now set up for you to connect a web page's chat bot to the skill.


section 2Install a Test Remote Server with JWT Token Generator Capability

For the purposes of this tutorial, you'll use a Node server, which we provide, that runs on your local machine and listens to the localhost URL. The server hosts the token generator and will host your web app as well.

For the purposes of this tutorial, the token generator, by default, generates a user ID that's based on a random number and the current time. When the token expires after one hour (or when you reload the page), it will generate a new token with a different user ID, thus starting a new user session.

For production web clients, you might want to first invoke a user ID generator to get a static ID and pass it to the token generator so that all tokens have the same user ID for the length of the conversation. As with the token generator, you should host the user ID generator on a different server.

You shouldn't use this sample app in production for the following reasons:

  • In production, you should never use a token server that listens to localhost and doesn't require an HTTPS connection.
  • You should host the token generator on a separate server.
  • The token server should enforce a domain allowlist to ensure that only authorized web clients can get the signed tokens.
  • The client application should use secure protocol for its server and APIs. That is, the application should only be accessible using https://<path> and it should use https in all calls to REST APIs.

Note: We used the Express application generator (https://www.npmjs.com/package/express-generator) to generate the Node server. For the JWT token generator, we used jsonwebtoken (https://www.npmjs.com/package/jsonwebtoken). There are many third-party libraries that you can choose from to generate JWT tokens.

As mentioned in What Do You Need, you must have Node and the Node package manager (npm) installed on your machine before you do these steps. Also ensure that your machine has access to the Internet.

  1. Download websdkauth-01.5.zip and unzip its contents into a folder of your choice.

    This creates the root folder named websdkauth.

  2. Open a terminal window in the websdkauth folder.

  3. If your Internet access is through a proxy, you might need to do the following to be able to install the node modules from the Internet, which you do in the next step.

    • To get the external IP address of your proxy, open a browser and go to http://www.whatismyproxy.com/.
    • In the terminal window, enter these commands:
        $ npm config set proxy http://<external_ip>:80
      $ npm config set https-proxy http://<external_ip>:80

  4. Enter this npm command to install the node modules that the websdkauth package depends on.

    $ npm install 
    added 113 packages from 148 contributors in 6.596s
  5. Open routes/config.json in an editor.

    This JSON file is a registry that you use to configure the token server so that it can create tokens for Oracle Web SDK implementations.

  6. Provide the following config values for the second JSON object in the file (the one with the name set to webSdkAuth):

    Property Value
    channelSecret The secret key for the channel that you created in Set Up the Oracle Web User Channel.
    channelId The channel ID for the channel that you created in Set Up the Oracle Web User Channel.

    Here's an example:

      {
        "name": "webSdkAuth",
        "config": {  
          "ignoreExternalUserId": true,      
          "exp": 3600,
          "channelSecret": "AbcDEFghiJK1LM2noPq45rs246as9kww",
          "channelId":     "abc123de-4f56-7g8h-jkl9-12m345n67o89"
        }
      }
  7. Save and close the file.

  8. (Optional) Open routes/token.js and search for if (confObject). You'll see this code:

        if (confObject) {
            var payload = {};
            var userIdParam = (req.query.userId) ?
              req.query.userId : null;
            // This code to generate a user ID is for 
            // use in the context of the tutorial and
            // is not intended for production
      
            // If userId query parameter passed in and
            // ignoreExternalUserId is set to false,
            // USE USERID FROM REQUEST
      
            // If no userId query parameter or if ignoreExternalUserId 
            // is set to true or is missing, then use 
            // GENERATE RANDOM USERID
            if (userIdParam && confObject.config.ignoreExternalUserId == false) {
      
              console.log('userId query parameter passed '
              + 'and ignoreExternalUserId is FALSE. '
              + 'Using userId query parameter.');            
              payload.userId = userIdParam;            
            } else {
              if (userIdParam) {
                console.log('userId query parameter passed, '
                  + 'but config.json ignoreExternalUserId is TRUE or is missing. '
                  + 'Ignoring userId query parameter and generating user ID.');          
              } else {
                console.log('Either no userId query parameter, '
                  + 'ignoreExternalUserId is set to true, or it is missing. '
                  + 'Generating user ID.');
              }
              payload.userId = getRandomArbitrary(1000, 9999999);      
            }
            console.log('userId set to: ' + payload.userId);
      
            payload.channelId = confObject.config.channelId;
      
            // Create and return JWT token. If expiry time is not set
            // in configuration file, set it to 1 hour.    
            try {
              var token = jwt.sign(payload, confObject.config.channelSecret, { algorithm: 'HS256', expiresIn: confObject.config.exp });
              res.status(200).send({ 'token': token });
            }
            catch (e) {
              console.log('Could not sign payload \n\n' + e);
              res.status(500).send('Internal error');
            }
          }
    

    The code first generates a user ID if one isn't passed in as a request query parameter and ignoreExternalUserId is set to false). It then retrieves the channel ID from the routes/config.json file, and creates the payload by adding the user ID and channel ID values. Then it calls the jsonwebtoken module's sign method to sign the payload with the secret key, which also was retrieved from routes/config.json.

    Notice that that the algorithm property in the call to the sign method is set to HS256.

    As you can see in the code, the token generator does take a user ID as an optional request parameter, but you must also set ignoreExternalUserId: false in the config.js file. In this tutorial, you don't use that feature and you generate the user ID instead.

Your Node server and token generator are ready. Next, you'll add your web app to the server.


section 3Create Your Web App

To create your web app, you'll copy the sample app from the Oracle Web SDK download and configure it to access your skill. Then you'll enable client authentication and add code to get a JWT token.

  1. Go to the folder that contains the Oracle Web SDK and copy the contents of samples/web.

  2. Go to the websdkauth folder that you created in the previous section, and then paste the copied content into the public subfolder.

    The public folder should now contain index.html and package.json files as well as images, scripts, and styles subfolders, as shown here:

    Screenshot of files and folders as described.

  3. To configure the web chat widget to connect to the skill and to call the token generator, open public/scripts/settings.js in an editor.

  4. At the top of the file, find this statement:

    const isClientAuthEnabled = false;

    Change false to true

    const isClientAuthEnabled = true;
  5. Remove these functions:

    • mockApiCall
    • generateJWTToken
    • generateToken

  6. Replace the functions that you just removed with this code:

    Tip: You can click Copy Copy to copy the code in the code box.

    /**
     * Use this same value for the name property in the 
     * token server's routes/config.json file as you
     * use for the APP_NAME.
     */
    const APP_NAME = 'webSdkAuth'
    const TOKEN_SERVER_ENDPOINT = 'http://localhost:3000/token' 
    /**
     * Function to generate JWT tokens. It returns a Promise to provide tokens.
     * The function is passed to SDK which uses it to fetch token whenever it needs
     * to establish a connection to the chat server. If a user ID isn't passed in, 
     * as in this function, then the token server generates one.
     * @returns {Promise} Promise to provide a signed JWT token
     */
    var generateToken = function () {                 
        return new Promise((resolve) => {
            fetch(TOKEN_SERVER_ENDPOINT +
            '?config=' + APP_NAME).then((
                response) => {
                    if (response.status !== 200) {
                        console.log(
                        'Looks like there was a problem. Status Code: ${response.status}.');
                        return;
                    }   
                    // Examine the text in the response
                    response.json().then(function (data) {                           
                        resolve(data.token);                           
                    });
                })
        });
    }
  7. In the initSdk method, change the chatWidgetSettings object's URI property value to your Digital Assistant instance's fully-qualified domain name. For example:

            let chatWidgetSettings = {
                URI: 'idcs-oda-xxxx.data.digitalassistant.oci.oc-test.com', // ODA URI, only the hostname part should be passed, without the https://
    
  8. Remove the channelId and userId properties, and then replace them with this line, which automatically opens the chat window:

                openChatOnLoad: true,

    The channel ID will be passed in the JWT token, and is based on the channelId that you earlier set in the routes/config file.

    The token generator will generate the userId and pass it in the JWT token.

  9. Set a value for the chatTitle, such as Chat.

  10. (Optional) If your skill requires user profile information, add code to just before the // Initialize SDK comment to set the required profile values. You only need to do this if your skill expects the web client to pass in information. In this example, the skill needs the client to pass in the first name, last name, email address, timezone offset, and locale, as well as a custom property. You would use similar code to pass in the necessary profile information for your skill. But you can skip this step if it isn't needed.

    
            var d = new Date();
            var timezoneOffset = d.getTimezoneOffset();
            /**
            * If your skill expects the web client to pass in profile values,
            * set them here or in index.html. For example:
            */
            chatWidgetSettings.initUserProfile = {
            profile: {
                userId: "joe.doe@example.com",
                firstName: 'joe',
                lastName: 'doe',
                email: 'joe.doe@example.com',
                timezoneOffset: timezoneOffset,
                locale: chatWidgetSettings.locale,
                properties: {
                    lastOrderedItems: '1 medium pepperoni'
                }
            }
            };
    

    Note that the profile information is not relevant to client authentication nor is it a means of user authentication or security.

  11. (Optional) Take a look at the code that follows // Initialize SDK and notice that when isClientAuthEnabled is set to true, it establishes the connection with a generated token.

            // Initialize SDK
            if (isClientAuthEnabled) {
                Bots = new WebSDK(chatWidgetSettings, generateToken);
            }...
  12. Save and close the file.

You can see a completed version of settings.js here.

You are now ready to test your secured access to the skill.


section 4Test Your Web App

To test your web app's secured access to the skill, you'll start the node server on port 3000 and access the web app through localhost.

  1. To start the node server, open a terminal window in the websdkauth folder, and then enter this command:

    $ npm start
    > sdkclientauthentication@0.0.0 start <path>\websdkauth
    > node ./bin/www
  2. To start the web app, go to this URL from your browser:

    localhost:3000

    Your web app should display with the chat window opened.

    Screenshot of the web app with the chat window opened.
  3. If the chat widget isn't open, click the chat icon shown here to open it.

    Chat icon
  4. Converse with the skill to verify that your web app is properly configured for secure access.

    If the skill doesn't respond, open the web console to diagnose the problem. For example, with Chrome, click the menu icon, click More Tools > Developer Tools, and then click the Application tab.

    Browser with application tool and cmd window

    Some typical errors are caused by including the protocol in the URI and by providing an incorrect secret key or channel ID.

  5. When you are done testing the web app, stop the Node server by pressing Ctrl+C in the terminal window that you used to start the server.


more informationWant to Learn More?