Single sign-on (SSO) support for messaging extensions

Single sign-on support is now available for messaging extensions and link unfurling. Enabling Single sign-on (SSO) for messaging extensions silently refreshes the authentication token, which minimizes the number of times you need to enter your sign in credentials for Microsoft Teams.

This document guides you on how to enable the SSO and store your authentication token, if required.

Prerequisites

The prerequisite to enable SSO for messaging extensions and link unfurling are as follows:

Note

For more information on creating an Azure account and updating your app manifest, see Single sign-on (SSO) support for bots.

After the prerequisites are completed, you can enable SSO for messaging extensions and link unfurling.

To enable SSO

  1. Update your bots OAuth connection details in the Azure portal.

  2. Download the messaging extensions sample and follow the setup instructions provided by the wizard.

    Note

    Use your bots OAuth connection when setting up your messaging extensions.

  3. In the TeamsMessagingExtensionsSearchAuthConfigBot.cs file, update the value from auth to silentAuth in the OnTeamsMessagingExtensionQueryAsync and / or OnTeamsAppBasedLinkQueryAsync.

    Note

    We do not support other handlers SSO, except OnTeamsMessagingExtensionQueryAsync and OnTeamsAppBasedLinkQueryAsync from the TeamsMessagingExtensionsSearchAuthConfigBot.cs file.

  4. You receive the token in OnTeamsMessagingExtensionQueryAsync handler in the turnContext.Activity.Value payload or in the OnTeamsAppBasedLinkQueryAsync, depending on which scenario you are enabling the SSO for:

    JObject valueObject=JObject.FromObject(turnContext.Activity.Value);
    if(valueObject["authentication"] !=null)
     {
        JObject authenticationObject=JObject.FromObject(valueObject["authentication"]);
        if(authenticationObject["token"] !=null)
     }
    
    

    If you are using the OAuth connection, add the following code to the TeamsMessagingExtensionsSearchAuthConfigBot.cs file to update or add the token in the store:

    protected override async Task<InvokeResponse> OnInvokeActivityAsync(ITurnContext<IInvokeActivity> turnContext, CancellationToken cancellationToken)
         {
             JObject valueObject = JObject.FromObject(turnContext.Activity.Value);
             if (valueObject["authentication"] != null)
             {
                 JObject authenticationObject = JObject.FromObject(valueObject["authentication"]);
                 if (authenticationObject["token"] != null)
                 {
                     //If the token is NOT exchangeable, then return 412 to require user consent
                     if (await TokenIsExchangeable(turnContext, cancellationToken))
                     {
                         return await base.OnInvokeActivityAsync(turnContext, cancellationToken).ConfigureAwait(false);
                     }
                     else
                     {
                         var response = new InvokeResponse();
                         response.Status = 412;
                         return response;
                     }
                 }
             }
             return await base.OnInvokeActivityAsync(turnContext, cancellationToken).ConfigureAwait(false);
         }
         private async Task<bool> TokenIsExchangeable(ITurnContext turnContext, CancellationToken cancellationToken)
         {
             TokenResponse tokenExchangeResponse = null;
             try
             {
                 JObject valueObject = JObject.FromObject(turnContext.Activity.Value);
                 var tokenExchangeRequest =
                 ((JObject)valueObject["authentication"])?.ToObject<TokenExchangeInvokeRequest>();
                 tokenExchangeResponse = await (turnContext.Adapter as IExtendedUserTokenProvider).ExchangeTokenAsync(
                  turnContext,
                  _connectionName,
                  turnContext.Activity.From.Id,
                  new TokenExchangeRequest
                  {
                      Token = tokenExchangeRequest.Token,
                  },
                  cancellationToken).ConfigureAwait(false);
             }
     #pragma warning disable CA1031 //Do not catch general exception types (ignoring, see comment below)
             catch
     #pragma warning restore CA1031 //Do not catch general exception types
             {
                 //ignore exceptions
                 //if token exchange failed for any reason, tokenExchangeResponse above remains null, and a failure invoke response is sent to the caller.
                 //This ensures the caller knows that the invoke has failed.
             }
             if (tokenExchangeResponse == null || string.IsNullOrEmpty(tokenExchangeResponse.Token))
             {
                 return false;
             }
             return true;
         }
    
    

See also