Add single sign on to a bot

APPLIES TO: SDK v4

This article shows how to use the Single sign on (SSO) feature in a bot. To do so, it uses a consumer bot, also known as root bot, to interact with a skill bot.

Once the users sign in the root bot, they are not required to sign into each skill bot they might use through the root bot. This is because of SSO. Without it the users would have to sign in every time they communicate with a different skill bot.

Note

The consumer bot is also called root or parent bot. The skill bot is also called child bot.
This article uses the terms root bot and skill bot.
With skills, the root and skill are separate bots, running on potentially different servers, each with its own separate memory and state.

Web Chat and Direct Line considerations

Important

When you use Azure Bot Service authentication with Web Chat there are some important security considerations you must keep in mind. For more information, see the security considerations section in the REST authentication article.

Prerequisites

Sample BotBuilder version Demonstrates
SSO with Simple Skill Consumer and Skill in CSharp v4 SSO support

About the samples

This article references two samples: the RootBot and the SkillBot. The RootBot forwards activities to the SkillBot. They model this typical skill scenario:

  • A root bot calls one or more skill bots.
  • Both the root and skill bots implement the basic authentication described in the Add authentication to a bot article.
  • The user logs into root bot.
  • Because of the SSO and being already logged into the root bot, she is logged into the skill bot without requiring user interaction again.

For an overview of how the Bot Framework handles authentication, see User authentication. For SSO background information, see Single sign on.

The RootBot supports user's SSO. It communicates with the SkillBot on behalf of the user, without the user being required to authenticate again into the SkillBot.

For each project in the sample, you need the following:

  1. An Azure AD application to register a bot resource in Azure.
  2. An Azure AD identity provider application for authentication.

    Note

    Currently, only the Azure AD v2 identity provider is supported.

Create the Azure RootBot resource

  1. Create an Azure bot resource in the Azure portal for the RootBot. Follow the steps described in Create an Azure bot resource.
  2. Copy and save the bot registration app ID and the client secret.

Create the Azure AD identity for RootBot

The Azure AD is a cloud identity service that allows you to build applications that securely sign in users using industry standard protocols like OAuth2.0.

  1. Create an identity application for the RootBot that uses Azure AD v2 to authenticate the user. Follow the steps described in Create the Azure AD identity provider.

  2. In the left pane, click Manifest.

  3. Set accessTokenAcceptedVersion to 2.

  4. Click Save.

  5. In the left pane, click Expose an API.

  6. In the right pane, click Add a scope.

  7. In the far right Add a scope section click Save and continue.

  8. In the displayed window, under Who can consent? select Admins and users.

  9. Enter the remaining required information.

  10. Click Add scope.

  11. Copy and save the scope value.

Create an OAuth connection settings

  1. Create an Azure AD v2 connection in the RootBot bot registration and enter values as described in Azure AD v2 and the value described below.

  2. Leave the Token Exchange URL empty.

  3. In the Scopes box enter the RootBot scope value you saved in the previous steps.

    Note

    The scopes contains the URL that the user initially signs in into the root bot, while the token exchange URL is left empty.

    As an example, let's assume that the root bot appid is rootAppId and the skill bot appid is skillAppId. The root bot's scopes will look like api://rootAppId/customScope, which is used to login the user. This root bot's scopes is then exchanged with api://skillAppId/customscope during SSO.

  4. Copy and save the name of the connection.

Create the Azure SkillBot resource

  1. Create an Azure bot resource in the Azure portal for the SkillBot. Follow the steps described in Create an Azure bot resource.
  2. Copy and save the bot registration app ID and the client secret.

Create the Azure AD identity for SkillBot

The Azure AD is a cloud identity service that allows you to build applications that securely sign in users using industry standard protocols like OAuth2.0.

  1. Create an identity application for the SkillBot that uses Azure AD v2 to authenticate the bot. Follow the steps described in Create the Azure AD identity provider.

  2. In the left pane, click Manifest.

  3. Set accessTokenAcceptedVersion to 2.

  4. Click Save.

  5. In the left pane, click Expose an API.

  6. In the right pane, click Add a scope.

  7. In the far right Add a scope section click Save and continue.

  8. In the displayed window, under Who can consent? select Admins and users.

  9. Enter the remaining required information.

  10. Click Add scope.

  11. Copy and save the scope value.

  12. Click Add a client application. In the far right section, in the Client ID box, enter the RootBot identity app ID you saved before. Make sure you use the RootBot identity and not the registration app ID.

  13. Under Authorized scope, check the box by the scope value.

  14. Click Add application.

  15. In the navigation pane on the left, click API permissions. It is a best practice to explicitly set the API permissions for the app.

    1. In the right pane, click Add a permission.

    2. Select Microsoft APIs then Microsoft Graph.

    3. Choose Delegated permissions and make sure the permissions you need are selected. This sample requires the permissions listed below.

      Note

      Any permission marked as ADMIN CONSENT REQUIRED will require both a user and a tenant admin to login.

      • openid
      • profile
      • User.Read
      • User.ReadBasic.All
    4. Click Add permissions.

Create an OAuth connection settings

  1. Create an Azure AD v2 connection in the SkillBot bot registration and enter values as described in Azure AD v2 and the values described below.

  2. In the Token Exchange URL box enter the SkillBot scope value you saved in the previous steps.

  3. In the Scopes box enter the following values separated by blank space: profile User.Read User.ReadBasic.All openid.

  4. Copy and save to a file the name of the connection.

Test the connection

  1. Click on the connection entry to open the connection you just created.
  2. Click Test Connection at the top of the Service Provider Connection Setting pane.
  3. The first time, this should open a new browser tab listing the permissions your app is requesting and prompt you to accept.
  4. Click Accept.
  5. This should then redirect you to a Test Connection to <your-connection-name> Succeeded page.

For more information, see the Azure Active Directory for developers (v1.0) overview and Microsoft identity platform (v2.0) overview. For information about the differences between the v1 and v2 endpoints, see Why update to Microsoft identity platform (v2.0)?. For complete information, see Microsoft identity platform (formerly Azure Active Directory for developers).

Prepare the samples code

You must update the appsettings.json file in both samples as described below.

  1. From the GitHub repository clone the SSO with Simple Skill Consumer and Skill sample.

  2. Open the SkillBot project appsettings.json file. From the saved file, assign the following values:

    {
        "MicrosoftAppId": "<SkillBot registration app ID>",
        "MicrosoftAppPassword": "<SkillBot registration password>",
        "ConnectionName": "<SkillBot connection name>",
        "AllowedCallers": [ "<RootBot registration app ID>" ]
    }
    
    
  3. Open the RootBot project appsettings.json file. From the saved file, assign the following values:

    {
        "MicrosoftAppId": "<RootBot registration app ID>",
        "MicrosoftAppPassword": "<RootBot registration password>",
        "ConnectionName": "<RootBot connection name>",
        "SkillHostEndpoint": "http://localhost:3978/api/skills/",
        "BotFrameworkSkills": [
                {
                "Id": "SkillBot",
                "AppId": "<SkillBot registration app ID>",
                "SkillEndpoint": "http://localhost:39783/api/messages"
                }
            ]
    }
    

Test the samples

Use the following for testing:

  • RootBot commands

    • login allows the user to sign into the Azure AD registration using the RootBot. Once signed in, SSO takes care of the sign in into the the SkillBot also. The user does not have to sign in again.
    • token displays the user's token.
    • logout logs the user out of the RootBot.
  • SkillBot commands

    • skill login allows the RootBot to sign into the SkillBot, on behalf of the user. The user is not shown a sign in card, if already signed in, unless SSO fails.
    • skill token displays the user's token from the SkillBot.
    • skill logout logs the user out of the SkillBot

Note

The first time users try SSO on a skill, they may be presented with an OAuth card to log in. This is because they have not yet given consent to the skill's Azure AD app. To avoid this, they can grant admin consent for any graph permissions requested by the Azure AD app.

Test using the Emulator

If you have not done so already, install the Bot Framework Emulator. See also Debug with the Emulator.

In order for the bot sample login to work you must configure the Emulator as shown in Configure the Emulator for authentication.

After you have configured the authentication mechanism, you can perform the actual bot sample testing.

  1. In Visual Studio, open the SSOWithSkills.sln solution and configure it to start debugging with multiple processes.

  2. Start debugging locally on your machine. Notice that in theRootBot project appsettings.json file you have the following settings:

        "SkillHostEndpoint": "http://localhost:3978/api/skills/"
        "SkillEndpoint": "http://localhost:39783/api/messages"
    

    Note

    These settings imply that, with both RootBot and SkillBot are running on the local machine. The Emulator communicates with RootBot on port 3978 and RootBot communicates with SkillBot on port 39783. As soon as you start debugging, two default browser windows open. One on port 3978 and the other on port 39783.

  3. Start the Emulator.

  4. You need to provide your RootBot registration app ID and password when you connect to the bot.

  5. Type hi to start the conversation.

  6. Enter login. The RootBot will display a Sign In to AAD authentication card.

    Root sign in

  7. Click Sign In. The pop-up dialog Confirm Open URL is displayed.

    Root confirm url

  8. Click Confirm. You will be logged in and the RootBot token is displayed.

  9. Enter token to display the token again.

    Root token image

    Now you are ready to communicate with the SkillBot. Once you've signed using the RootBot, you don't need to provide your credentials again until you sign out. This demonstrates that SSO is working.

  10. Enter skill login in the Emulator box. You will not be asked to login again. Instead the SkillBot token is displayed.

  11. Go ahead enter skill token to display the token again.

    Skill token image

  12. Now you can enter skill logout to sign out of the SkillBot. Then enter logout to sign out of the SimpleRootBoot.

Additional information

The following time-sequence diagram applies to the samples used in the article and shows the interaction between the various components involved. ABS stands for Azure Bot Service.

Skill token flow

  1. The first time, the user enters the login command for the RootBot.
  2. The RootBot sends an OAuthCard asking the user to sign in.
  3. The user enters the authentication credentials that are sent to the ABS (Azure Bot Service).
  4. The ABS sends the authentication token, generated based on the user's credentials, to the RootBot.
  5. The RootBot displays the root token for the user to see.
  6. The user enters the skill login command for the SkillBot.
  7. The SkillBot sends an OAuthCard to the RootBot.
  8. The RootBot asks for an exchangeable token from ABS.
  9. At this point the SSO "dance" comes into play which ends with the skill token sent by the SkillBot to the RootBot.
  10. The RootBot displays the skill token for the user to see. Notice that the skill token was generated without the user having to sign in the SKillBot. This is because of the SSO.

To see how the token exchange happens, please refer to the example shown below. The function can be found in TokenExchangeSkillHandler.cs.

private async Task<bool> InterceptOAuthCards(ClaimsIdentity claimsIdentity, Activity activity)
{
    var oauthCardAttachment = activity.Attachments?.FirstOrDefault(a => a?.ContentType == OAuthCard.ContentType);
    if (oauthCardAttachment != null)
    {
        var targetSkill = GetCallingSkill(claimsIdentity);
        if (targetSkill != null)
        {
            var oauthCard = ((JObject)oauthCardAttachment.Content).ToObject<OAuthCard>();

            if (!string.IsNullOrWhiteSpace(oauthCard?.TokenExchangeResource?.Uri))
            {
                using (var context = new TurnContext(_adapter, activity))
                {
                    context.TurnState.Add<IIdentity>("BotIdentity", claimsIdentity);

                    // AAD token exchange
                    try
                    {
                        var result = await _tokenExchangeProvider.ExchangeTokenAsync(
                            context,
                            _connectionName,
                            activity.Recipient.Id,
                            new TokenExchangeRequest() { Uri = oauthCard.TokenExchangeResource.Uri }).ConfigureAwait(false);

                        if (!string.IsNullOrEmpty(result?.Token))
                        {
                            // If token above is null, then SSO has failed and hence we return false.
                            // If not, send an invoke to the skill with the token. 
                            return await SendTokenExchangeInvokeToSkill(activity, oauthCard.TokenExchangeResource.Id, result.Token, oauthCard.ConnectionName, targetSkill, default).ConfigureAwait(false);
                        }
                    }
                    catch
                    {
                        // Show oauth card if token exchange fails.
                        return false;
                    }

                    return false;
                }
            }
        }
    }
    return false;
}

Further reading