Embed a bot in a web page using Web Chat

This post describes how to embed a bot in a web page using the Bot Framework Web Chat. The Web Chat control is a highly-customizable web-based client for the Bot Framework V4 SDK. It uses the  Direct Line channel, associated with the bot, to allow the user to talk with the bot from within the web page.

In its essence, this post shows how to generate a Direct Line token without exposing the Direct Line secret. It does so by separating the server code, which generates the token, from the client code (web page) which uses the token to render the Web Chat, without exposing any secret. Even though the Server is running locally on http://localhost:3000, it is a simulated version of what you would do in an actual production environment.

Any bot registered with the Bot Framework is automatically configured for the Web Chat channel which includes the Web Chat control to allow communication with the bot. As described in the Connect a bot to Web Chat article, a common way to embed a bot in a website is using an iframe HTML element. The problem with the approach described in the article is that it exposes the Web Channel secret in the client web page. This can be acceptable during development but not in production.

WARNING! In production, it is strongly recommend that you use a token instead of your secret. See Secrets and tokens .

Download the example code here.

Giving credit. The example code shown in this post has been adapted from User-specific Direct Line token sample. There you can find a more complete implementation that includes the JavaScript version. Many thanks to Nafis Zaman.

Prerequisites

Example Components

The following figure shows the components involved in the example used in this post.

  1. Server. Also known as the API, it performs the Direct Line token acquisition. It verifies the user’s identity and acquires a Direct Line token bound to that identity.
  2. Client. A static index.html page that can be hosted using any web server. It requires the user to sign in. Then it makes a request to the server with proof of the user’s identity. It obtains the Direct Line token from the sever and uses it to render the Web Chat.
  3. Bot The example assumes that you already have a bot requiring user’s authentication and deployed in Azure. See Tutorial: Create and deploy a basic bot

Security Considerations

Web Chat secret

When embedding a Web Chat in a web page, you can provide either a Direct Line secret or a Direct Line token so the Web Chat can communicate with the bot.

  • The Direct Line secret can be used to access all of the bot’s conversations; it doesn’t expire.
  • The Direct Line token can only be used to access a single conversation; it does expire. For more information, see the Direct Line Authentication article.

WARNING! The use of the Direct Line secret in an web page is strongly discouraged. The recommended approach is to exchange the secret for a token with the help of a Direct Line token generator server.

User impersonation

The Web Chat allows to specify a user ID, which is sent to the bot. Typically, the user ID is not verified and this may allow user impersonation by hackers.

WARNING! Unverified user IDs are a security risk if the bot stores sensitive data based on user ID. For example, the built-in user authentication support in Azure Bot Service associates access tokens with user IDs.

To avoid impersonation, the recommended approach is for the server to bind a user ID to the Direct Line token. Then any conversation using that token will send the bound user ID to the bot. However, if the client provides the user ID to the server, it is important for the server to validate the ID.

The preferred approach is to leverage a user’s existing identity from an identity provider. The user must first sign in to the site before talking to the bot. This prevents user impersonation because the user’s identity is verified by the identity provider.

Server Highlights

The simulation server runs on localhost port 3000 (localhost:3000) . It uses the stored Direct Line secret to obtain the Direct Line token, see the related commands in the Running the Example, Server section.  The following are the highlights of the server side logic:

  1. In the body of the POST request, the Web Chat send to the server an OpenID Connect (OIDC) JWT, called ID token, that identifies the user.
  2. The server validates the ID token against the chosen identity provider (Azure Active Directory in the example).
  3. It builds a user ID using claims from the validated token. The example uses the sub (subject) claim because it’s a standard OIDC claim that uniquely identifies the user, and it doesn’t require any additional scopes. Notice the following:
    1. In the Azure Active Directory, the sub claim is only consistent per user per application. This means the user ID wouldn’t be sufficient for looking up the user in other systems (such as Microsoft Graph). If we needed a user ID that identifies the user across applications, we could use the oid (object ID) claim, but it requires the profile scope. See AAD ID token claims for more details
    2. Since the user identity is verified, it is okay for this user ID to be a guessable value.
  4. It retrieves a user ID bound Direct Line token using the Direct Line API.
  5. It responds with the user-specific Direct Line token.

Client Highlights

Depending on the scenario, the Server API could be called from a client, such as a single-page application, or a server, such as a more traditional web app, where tokens are handled server-side. The only requirement is that the caller can provide a user ID token from the expected identity provider. If you are embedding the bot in an authenticated site, then you may already have an ID token that you can use.

The simulation client run on localhost port 5500 (localhost:5500) . It uses the stored Direct Line secret to obtain the Direct Line token, see the related commands in the Running the Example, Client section.

Code highlights

Receiving the ID token

The Server API expects the user ID token to be passed in the request body:

// DirectLineTokenController.cs

public class TokenRequest
{
    [JsonPropertyName("id_token")]
    public string idToken { get; set; }
}
...
public async Task<IActionResult> Post([FromBody] TokenRequest request)
{
    ...
}

Tokens are typically sent as bearer tokens in the Authorization header. However, the example does not use the user’s ID token to protect the API. Rather, it is a parameter of the request itself. Although the API isn’t protected, you could protect the API using a different token (such as an OAuth access token) which would go in the Authorization header. See also What is the OAuth 2.0 Bearer Token exactly?.

Validating the ID token

The example uses two packages to achieve token validation: Microsoft.IdentityModel.Protocols.OpenIdConnect and System.IdentityModel.Tokens.Jwt. The example first retrieves the Azure AD public keys used to sign the token and then validate the token:

// DirectLineTokenController.cs
var configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(
    "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration",
    new OpenIdConnectConfigurationRetriever(),
    new HttpDocumentRetriever());
...
var discoveryDocument = await configurationManager.GetConfigurationAsync(ct);
var signingKeys = discoveryDocument.SigningKeys;

// DirectLineTokenController.cs
var principal = new JwtSecurityTokenHandler()
    .ValidateToken(token, validationParameters, out var rawValidatedToken);

Calling the API and rendering Web Chat

After the user signs in, the client calls the Server API with the user’s ID token and uses the resulting Direct Line token to render the Web Chat.

Note that the page does not specify a user ID when initiating the Web Chat. Direct Line will handle sending the user ID to the bot based on the token.

// index.html
async function getDirectLineToken(idToken) {
    const res = await fetch('http://localhost:3000/api/direct-line-token', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({ id_token: idToken }),
    });
    ...
    return await res.json();
}
...
const directLineTokenResponse = await getDirectLineToken(idToken);
...
WebChat.renderWebChat(
    {
        directLine: WebChat.createDirectLine({ token: directLineTokenResponse.token }),
    },
    document.getElementById('webchat')
);

Run the example

There are 2 sides involved when running the example: server and client.

Download the example code here.

Server

To run the server application you will use the dotnet command specifically dotne user-secrets set  to store the Direct Line secret and dotnet run to run the server application from the source code.

In a terminal window execute the following commands:

  1. Switch to the server directory, where the server API application is: cd ..server\csharp.
  2. In your code editor, open the appsettings.json file. Assign the following value: "TokenValidationSettings": {"ValidAudience": "<Your Azure AD app ID>" }.
  3. Store the Direct Line secret: dotnet user-secrets set "DirectLine:DirectLineSecret" "<Your Direct Line Secret>".
  4. Run the server API: dotnet run.

 

Variable Value Description
"TokenValidationSettings": {"ValidAudience": "<Your Azure AD app ID>" }  <Your Azure AD identity provider app ID> The Azure AD identity provider app ID. You assign this value in the appsettings.json file.
<DirectLine:DirectLineSecret>  <Your Direct Line secret> The Direct Line secret associated with the bot.

Client

After receiving the Direct Line token, the caller can then use it to render the Web Chat. The bot will receive a consistent user ID that it can rely upon.

In a terminal window execute the following commands:

  1. Switch to the client directory, where the index.html page is:  cd ..client.
  2. Open the index.html in your favorite editor, and assign  the following values to the empty variables :
    Variable Value Description
    AAD_APP_ID <Your Azure AD identity provider app ID> The Azure AD identity provider app ID. Used to authenticate the bot’s users.
    AAD_REDIRECT_URI  http://localhost:5500 Remember to add the redirect URI http://localhost:5500to your Azure AD identity provider.
  3. In order to “serve” the web page on localhost:5500 you will use the npx http-server command to run a local web server.
    Run a local web server on the specified port:  npx http-server ./ -p 5500. 
  4. In your browser navigate to http://localhost:5500. This will display the static index.html page and activates the Web Chat.
  5. You can start chatting with the bot.

The following figures show an example of a simple and a customized Web Chat:

Simple Customized