From 974b21149e7d7dedf66c61e5d58a6d8782e6aca4 Mon Sep 17 00:00:00 2001 From: v-nikitach Date: Mon, 25 May 2026 14:52:41 +0530 Subject: [PATCH 1/7] made updates in two files --- .../how-to/authentication/bot-sso-code.md | 517 ++++-------- .../messaging-extensions/msgex-sso-code.md | 792 +++++++----------- 2 files changed, 437 insertions(+), 872 deletions(-) diff --git a/msteams-platform/bots/how-to/authentication/bot-sso-code.md b/msteams-platform/bots/how-to/authentication/bot-sso-code.md index e6fe22ee464..7acdeb49e0c 100644 --- a/msteams-platform/bots/how-to/authentication/bot-sso-code.md +++ b/msteams-platform/bots/how-to/authentication/bot-sso-code.md @@ -4,7 +4,7 @@ description: Learn how to add code configuration, handle an access token, receiv ms.topic: how-to ms.localizationpriority: high zone_pivot_groups: enable-sso -ms.date: 11/13/2024 +ms.date: 05/25/2026 --- # Add code to enable SSO in your bot app @@ -23,8 +23,9 @@ You need to configure your app's code to obtain an access token from Microsoft E This section covers: 1. [Update development environment variables](#update-development-environment-variables) -1. [Add code to handle an access token](#add-code-to-handle-an-access-token) -1. [Add code to receive the token](#add-code-to-receive-the-token) +1. [Initialize the app with OAuth](#initialize-the-app-with-oauth) +1. [Handle sign-in and token receipt](#handle-sign-in-and-token-receipt) +1. [Handle sign-in failures](#handle-sign-in-failures) 1. [Handle app user sign out](#handle-app-user-sign-out) ## Update development environment variables @@ -34,214 +35,66 @@ You've configured client secret and OAuth connection setting for the app in Micr To update the development environment variables: 1. Open the bot app project. -1. Open the environment file for your project. +1. Open the environment file (`.env`) for your project. 1. Update the following variables: - - For `MicrosoftAppId`, update the bot ID from Microsoft Entra ID. - - For `MicrosoftAppPassword`, update the client secret. - - For `ConnectionName`, update the name of the OAuth connection you configured in Microsoft Entra ID. - - For `MicrosoftAppTenantId`, update the tenant ID. + - For `CLIENT_ID`, update the bot ID from Microsoft Entra ID. + - For `CLIENT_SECRET`, update the client secret. + - For `CONNECTION_NAME`, update the name of the OAuth connection you configured in Microsoft Entra ID. + - For `TENANT_ID`, update the tenant ID. > [!NOTE] > You can customize the OAuth redirect URL for your bot and identity provider based on your data residency requirements, irrespective of whether your bot is in the public cloud, Microsoft Azure Government cloud, or Microsoft Azure operated by 21Vianet. For OAuth URLs and data residency list, see [OAuth URL support in Azure AI Bot Service](/azure/bot-service/ref-oauth-redirect-urls?view=azure-bot-service-4.0&preserve-view=true). 1. Save the file. -You've now configured the required environment variables for your bot app and SSO. Next, add the code for handling bot tokens. +You've now configured the required environment variables for your bot app and SSO. Next, initialize the app with OAuth. -> [!div class="nextstepaction"] -> [I ran into an issue](https://github.com/MicrosoftDocs/msteams-docs/issues/new?template=Doc-Feedback.yaml&title=%5BI+ran+into+an+issue%5D+Update+development+environment+variables&&author=%40surbhigupta&pageUrl=https%3A%2F%2Flearn.microsoft.com%2Fen-us%2Fmicrosoftteams%2Fplatform%2Fbots%2Fhow-to%2Fauthentication%2Fbot-sso-code%3Ftabs%3Dcs1%252Ccs2%252Ccs3%252Ccs4%252Ccs5%26pivots%3Dbot-app%23update-development-environment-variables&contentSourceUrl=https%3A%2F%2Fgithub.com%2FMicrosoftDocs%2Fmsteams-docs%2Fblob%2Fmain%2Fmsteams-platform%2Fbots%2Fhow-to%2Fauthentication%2Fbot-sso-code.md&documentVersionIndependentId=039ff5cc-7243-ce4b-527e-c152755eeb72&platformId=915789b2-9617-01bb-fb21-d6789a634ed8&metadata=*%2BID%253A%2Be473e1f3-69f5-bcfa-bcab-54b098b59c80%2B%250A*%2BService%253A%2B%2A%2Amsteams%2A%2A) +## Initialize the app with OAuth -## Add code to handle an access token +The Teams SDK simplifies app initialization with a single `App` class that handles server lifecycle, authentication, and token exchange internally. -The request to get the token is a POST message request using the existing message schema. It's included in the attachments of an OAuthCard. The schema for the OAuthCard class is defined in [Microsoft Bot Schema 4.0](/dotnet/api/microsoft.bot.schema.oauthcard?view=botbuilder-dotnet-stable&preserve-view=true). Teams refreshes the token if the `TokenExchangeResource` property is populated on the card. For the Teams channel, only the `Id` property, which uniquely identifies a token request, is honored. +# [C#](#tab/cs1) ->[!NOTE] -> The Microsoft Bot Framework `OAuthPrompt` or the `MultiProviderAuthDialog` is supported for SSO authentication. +```csharp +using Microsoft.Teams.Apps.Extensions; +using Microsoft.Teams.Plugins.AspNetCore.Extensions; -To update your app's code: +var builder = WebApplication.CreateBuilder(args); -1. Add code snippet for `TeamsSSOTokenExchangeMiddleware`. +var appBuilder = App.Builder() + .AddOAuth("graph"); - # [C#](#tab/cs1) +builder.AddTeams(appBuilder); +var app = builder.Build(); +var teams = app.UseTeams(); +``` - Add the following code snippet to `AdapterWithErrorHandler.cs` (or the equivalent class in your app's code): +# [TypeScript](#tab/ts1) - ```csharp - base.Use(new TeamsSSOTokenExchangeMiddleware(storage, configuration["ConnectionName"])); - ``` +```typescript +import { App } from '@microsoft/teams.apps'; - # [JavaScript](#tab/js1) +const app = new App({ + oauth: { + defaultConnectionName: 'graph', + }, +}); +``` - Add the following code snippet to `index.js` (or the equivalent class in your app's code): +# [Python](#tab/py1) - ```JavaScript - const {TeamsSSOTokenExchangeMiddleware} = require('botbuilder'); - const tokenExchangeMiddleware = new TeamsSSOTokenExchangeMiddleware(memoryStorage, env.connectionName); - adapter.use(tokenExchangeMiddleware); - ``` +```python +import os +from teams import App - --- +app = App(default_connection_name=os.getenv("CONNECTION_NAME", "graph")) +``` - > [!NOTE] - > You might receive multiple responses for a given request if the user has multiple active endpoints. You must eliminate all duplicate or redundant responses with the token. For more information about signin/tokenExchange, see [TeamsSSOTokenExchangeMiddleware Class](/python/api/botbuilder-core/botbuilder.core.teams.teams_sso_token_exchange_middleware.teamsssotokenexchangemiddleware?view=botbuilder-py-latest&preserve-view=true#remarks). - -1. Use the following code snippet for requesting a token. - - # [C#](#tab/cs2) - - After you add the `AdapterWithErrorHandler.cs`, the following code must appear: - - ```csharp - public class AdapterWithErrorHandler : CloudAdapter - { - public AdapterWithErrorHandler( - IConfiguration configuration, - IHttpClientFactory httpClientFactory, - ILogger logger, - IStorage storage, - ConversationState conversationState) - : base(configuration, httpClientFactory, logger) - { - base.Use(new TeamsSSOTokenExchangeMiddleware(storage, configuration["ConnectionName"])); - - OnTurnError = async (turnContext, exception) => - { - // Log any leaked exception from the application. - // NOTE: In production environment, you must consider logging this to - // Azure Application Insights. Visit https://learn.microsoft.com/azure/bot-service/bot-builder-telemetry?view=azure-bot-service-4.0&tabs=csharp to see how - // to add telemetry capture to your bot. - logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}"); - - // Send a message to the user. - await turnContext.SendActivityAsync("The bot encountered an error or bug."); - await turnContext.SendActivityAsync("To continue to run this bot, please fix the bot source code."); - - if (conversationState != null) - { - try - { - // Delete the conversationState for the current conversation to prevent the - // bot from getting stuck in an error-loop caused by being in a bad state. - // conversationState must be thought of as similar to "cookie-state" in a Web pages. - await conversationState.DeleteAsync(turnContext); - } - catch (Exception e) - { - logger.LogError(e, $"Exception caught on attempting to Delete ConversationState : {e.Message}"); - } - } - - // Send a trace activity, which is displayed in the Bot Framework Emulator. - await turnContext.TraceActivityAsync( - "OnTurnError Trace", - exception.Message, - "https://www.botframework.com/schemas/error", - "TurnError"); - }; - } - } - ``` - - # [JavaScript](#tab/js2) - - After you add the code snippet for `TeamsSSOTokenExchangeMiddleware`, the following code must appear: - - ```JavaScript - // index.js is used to setup and configure your bot. - - // Import required packages - const path = require('path'); - - // Read botFilePath and botFileSecret from .env file. - const ENV_FILE = path.join(__dirname, '.env'); - require('dotenv').config({ path: ENV_FILE }); - - const express = require("express"); - - // Import required bot services. - // See https://learn.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0 to learn more about the different parts of a bot. - const { - CloudAdapter, - ConversationState, - MemoryStorage, - UserState, - ConfigurationBotFrameworkAuthentication, - TeamsSSOTokenExchangeMiddleware - } = require('botbuilder'); - - const { TeamsBot } = require('./bots/teamsBot'); - const { MainDialog } = require('./dialogs/mainDialog'); - const { env } = require('process'); - - const botFrameworkAuthentication = new ConfigurationBotFrameworkAuthentication(process.env); - - var conname = env.connectionName; - - console.log(`\n${ conname } is the con name`); - - // Create adapter. - // See https://learn.microsoft.com/javascript/api/botbuilder-core/botadapter?view=botbuilder-ts-latest to learn more about how bot adapter. - const adapter = new CloudAdapter(botFrameworkAuthentication); - const memoryStorage = new MemoryStorage(); - const tokenExchangeMiddleware = new TeamsSSOTokenExchangeMiddleware(memoryStorage, env.connectionName); - - adapter.use(tokenExchangeMiddleware); - adapter.onTurnError = async (context, error) => { - // This check writes out errors to console log .vs. app insights. - // NOTE: In production environment, you must consider logging this to Azure - // application insights. See https://learn.microsoft.com/azure/bot-service/bot-builder-telemetry?view=azure-bot-service-4.0&tabs=csharp for telemetry - // configuration instructions. - console.error(`\n [onTurnError] unhandled error: ${ error }`); - - // Send a trace activity, which is displayed in Bot Framework Emulator. - await context.sendTraceActivity( - 'OnTurnError Trace', - `${ error }`, - 'https://www.botframework.com/schemas/error', - 'TurnError' - ); - - // Send a message to the user. - await context.sendActivity('The bot encountered an error or bug.'); - await context.sendActivity('To continue to run this bot, please fix the bot source code.'); - // Clear out state. - await conversationState.delete(context); - }; - - // Define the state store for your bot. - // See https://aka.ms/about-bot-state to learn more about using MemoryStorage. - // A bot requires a state storage system to persist the dialog and user state between messages. - //const memoryStorage = new MemoryStorage(); - - // Create conversation and user state with in-memory storage provider. - const conversationState = new ConversationState(memoryStorage); - const userState = new UserState(memoryStorage); - - // Create the main dialog. - const dialog = new MainDialog(); - // Create the bot that will handle incoming messages. - const bot = new TeamsBot(conversationState, userState, dialog); - - // Create express application. - const expressApp = express(); - expressApp.use(express.json()); - - const server = expressApp.listen(process.env.port || process.env.PORT || 3978, () => { - console.log(`\n${ expressApp.name } listening to`, server.address()); - console.log('\nGet Bot Framework Emulator: https://aka.ms/botframework-emulator'); - console.log('\nTo talk to your bot, open the emulator select "Open Bot"'); - }); - - // Listen for incoming requests. - expressApp.post('/api/messages', async (req, res) => { - // Route received a request to adapter for processing. - await adapter.process(req, res, (context) => bot.run(context)); - }); - ``` +--- -> [!div class="nextstepaction"] -> [I ran into an issue](https://github.com/MicrosoftDocs/msteams-docs/issues/new?template=Doc-Feedback.yaml&title=%5BI+ran+into+an+issue%5D+Add+code+to+handle+an+access+token&&author=%40surbhigupta&pageUrl=https%3A%2F%2Flearn.microsoft.com%2Fen-us%2Fmicrosoftteams%2Fplatform%2Fbots%2Fhow-to%2Fauthentication%2Fbot-sso-code%3Ftabs%3Dcs1%252Ccs2%252Ccs3%252Ccs4%252Ccs5%26pivots%3Dbot-app%23add-code-to-handle-an-access-token&contentSourceUrl=https%3A%2F%2Fgithub.com%2FMicrosoftDocs%2Fmsteams-docs%2Fblob%2Fmain%2Fmsteams-platform%2Fbots%2Fhow-to%2Fauthentication%2Fbot-sso-code.md&documentVersionIndependentId=039ff5cc-7243-ce4b-527e-c152755eeb72&platformId=915789b2-9617-01bb-fb21-d6789a634ed8&metadata=*%2BID%253A%2Be473e1f3-69f5-bcfa-bcab-54b098b59c80%2B%250A*%2BService%253A%2B%2A%2Amsteams%2A%2A) +> [!NOTE] +> The `App` class handles all adapter configuration, middleware, error handling, and server setup internally. ## Consent dialog for getting access token @@ -321,229 +174,165 @@ If the user permissions are granted by default or for trusted apps, the user who If you encounter any errors, see [Troubleshoot SSO authentication in Teams](../../../tabs/how-to/authentication/tab-sso-troubleshooting.md). -## Add code to receive the token - -The response with the token is sent through an invoke activity with the same schema as other invoke activities that the bots receive today. The only difference is the invoke name, sign in/tokenExchange, and the **value** field. The **value** field contains the **Id**, a string of the initial request to get the token and the **token** field, a string value including the token. +## Handle sign-in and token receipt -Use the following code snippet to invoke response: +The Teams SDK uses simple event-driven handlers for authentication. Use `IsSignedIn` to check authentication status, `SignIn()` to trigger the SSO flow, and subscribe to the `signin` event to handle successful authentication. # [C#](#tab/cs3) ```csharp -public MainDialog(IConfiguration configuration, ILogger logger) - : base(nameof(MainDialog), configuration["ConnectionName"]) - { - AddDialog(new OAuthPrompt( - nameof(OAuthPrompt), - new OAuthPromptSettings - { - ConnectionName = ConnectionName, - Text = "Please Sign In", - Title = "Sign In", - Timeout = 300000, // User has 5 minutes to login (1000 * 60 * 5) - EndOnInvalidMessage = true - })); - - AddDialog(new ConfirmPrompt(nameof(ConfirmPrompt))); - - AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[] - { - PromptStepAsync, - LoginStepAsync, - })); - - // The initial child Dialog to run. - InitialDialogId = nameof(WaterfallDialog); - } - - -private async Task PromptStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) - { - return await stepContext.BeginDialogAsync(nameof(OAuthPrompt), null, cancellationToken); - } - -private async Task LoginStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) - { - - var tokenResponse = (TokenResponse)stepContext.Result; - if (tokenResponse?.Token != null) - { - var token = tokenResponse.Token; - - // On successful login, the token contains sign in token. - } - else - { - await stepContext.Context.SendActivityAsync(MessageFactory.Text("Login was not successful please try again."), cancellationToken); - } - - return await stepContext.EndDialogAsync(cancellationToken: cancellationToken); - } +teams.OnMessage(async (context, cancellationToken) => +{ + if (!context.IsSignedIn) + { + await context.SignIn(cancellationToken); + return; + } + + var token = context.UserToken; + await context.Send($"You are signed in. Token length: {token?.Length}", cancellationToken); +}); + +teams.OnSignIn(async (_, teamsEvent, cancellationToken) => +{ + var context = teamsEvent.Context; + await context.Send("Successfully signed in! You can now use the bot.", cancellationToken); +}); ``` -# [JavaScript](#tab/js3) +# [TypeScript](#tab/ts3) -```JavaScript -class MainDialog { - - this.addDialog(new OAuthPrompt(OAUTH_PROMPT, { - connectionName: process.env.connectionName, - text: 'Please Sign In', - title: 'Sign In', - timeout: 300000 - })); +```typescript +app.on('message', async ({ isSignedIn, signin, userToken, send }) => { + if (!isSignedIn) { + await signin(); + return; + } - this.addDialog(new ConfirmPrompt(CONFIRM_PROMPT)); + await send(`You are signed in. Token length: ${userToken?.length}`); +}); - this.addDialog(new WaterfallDialog(MAIN_WATERFALL_DIALOG, [ - this.promptStep.bind(this), - this.loginStep.bind(this), - ])); +app.event('signin', async ({ send, token }) => { + await send(`Successfully signed in! Token length: ${token.token.length}`); +}); +``` - this.initialDialogId = MAIN_WATERFALL_DIALOG; +# [Python](#tab/py3) - } +```python +from teams.api import MessageActivity, SignInEvent +from teams.apps import ActivityContext -async promptStep(stepContext) { - try { - return await stepContext.beginDialog(OAUTH_PROMPT); - } catch (err) { - console.error(err); - } - } +@app.on_message +async def handle_message(ctx: ActivityContext[MessageActivity]): + if not ctx.is_signed_in: + await ctx.sign_in() + return -async loginStep(stepContext) { - // Get the token from the previous step. Note that we could also have gotten the - // token directly from the prompt itself. There is an example of this in the next method. - const tokenResponse = stepContext.result; - if (!tokenResponse || !tokenResponse.token) { - await stepContext.context.sendActivity('Login was not successful please try again.'); - } else { - const token = tokenResponse.token; - // On successful login, the token contains sign in token. - } - return await stepContext.endDialog(); - } + token = ctx.user_token + await ctx.send(f"You are signed in. Token length: {len(token)}") + +@app.event("sign_in") +async def handle_sign_in(event: SignInEvent): + await event.activity_ctx.send("Successfully signed in! You can now use the bot.") ``` --- > [!NOTE] -> The code snippets use the Waterfall Dialog. For more information, see [About component and waterfall dialogs](/azure/bot-service/bot-builder-concept-waterfall-dialogs?view=azure-bot-service-4.0&preserve-view=true). +> The SDK handles the token exchange and validation internally. You no longer need to manually manage `OAuthPrompt`, `WaterfallDialog`, or `MainDialog` classes. -> [!div class="nextstepaction"] -> [I ran into an issue](https://github.com/MicrosoftDocs/msteams-docs/issues/new?template=Doc-Feedback.yaml&title=%5BI+ran+into+an+issue%5D+Add+code+to+receive+the+token&&author=%40surbhigupta&pageUrl=https%3A%2F%2Flearn.microsoft.com%2Fen-us%2Fmicrosoftteams%2Fplatform%2Fbots%2Fhow-to%2Fauthentication%2Fbot-sso-code%3Ftabs%3Dcs1%252Ccs2%252Ccs3%252Ccs4%252Ccs5%26pivots%3Dbot-app%23add-code-to-receive-the-token&contentSourceUrl=https%3A%2F%2Fgithub.com%2FMicrosoftDocs%2Fmsteams-docs%2Fblob%2Fmain%2Fmsteams-platform%2Fbots%2Fhow-to%2Fauthentication%2Fbot-sso-code.md&documentVersionIndependentId=039ff5cc-7243-ce4b-527e-c152755eeb72&platformId=915789b2-9617-01bb-fb21-d6789a634ed8&metadata=*%2BID%253A%2Be473e1f3-69f5-bcfa-bcab-54b098b59c80%2B%250A*%2BService%253A%2B%2A%2Amsteams%2A%2A) +## Handle sign-in failures -## Validate the access token +When using SSO, if the token exchange fails, Teams sends a `signin/failure` invoke activity to your app. The SDK includes a built-in default handler that logs a warning with actionable troubleshooting guidance. You can optionally register your own handler to customize the behavior: -Web APIs on your server must decode the access token and verify if it's sent from the client. +# [C#](#tab/cs-failure) -> [!NOTE] -> If you use Bot Framework, it handles the access token validation. If you don't use Bot Framework, follow the guidelines given in this section. - -For more information about validating access token, see [Validate tokens](/azure/active-directory/develop/access-tokens#validate-tokens). +```csharp +teams.OnSignInFailure(async (context, cancellationToken) => +{ + var failure = context.Activity.Value; + Console.WriteLine($"Sign-in failed: {failure?.Code} - {failure?.Message}"); + await context.Send("Sign-in failed. Please try again.", cancellationToken); +}); +``` -There are many libraries available that can handle JWT validation. Basic validation includes: +# [TypeScript](#tab/ts-failure) -- Checking that the token is well-formed. -- Checking that the token was issued by the intended authority. -- Checking that the token is targeted to the web API. -Keep in mind the following guidelines when validating the token: +```typescript +app.on('signin.failure', async ({ activity, send }) => { + const { code, message } = activity.value; + console.log(`Sign-in failed: ${code} - ${message}`); + await send('Sign-in failed. Please try again.'); +}); +``` -- Valid SSO tokens are issued by Microsoft Entra ID. The `iss` claim in the token must start with this value. -- The token's `aud1` parameter is set to the app ID generated during Microsoft Entra app registration. -- The token's `scp` parameter is set to `access_as_user`. +# [Python](#tab/py-failure) -### Example access token +```python +@app.on_signin_failure() +async def handle_signin_failure(ctx): + failure = ctx.activity.value + print(f"Sign-in failed: {failure.code} - {failure.message}") + await ctx.send("Sign-in failed. Please try again.") +``` -The following code snippet is a typical decoded payload of an access token: +--- -```javascript -{ - aud: "2c3caa80-93f9-425e-8b85-0745f50c0d24", - iss: "https://login.microsoftonline.com/fec4f964-8bc9-4fac-b972-1c1da35adbcd/v2.0", - iat: 1521143967, - nbf: 1521143967, - exp: 1521147867, - aio: "ATQAy/8GAAAA0agfnU4DTJUlEqGLisMtBk5q6z+6DB+sgiRjB/Ni73q83y0B86yBHU/WFJnlMQJ8", - azp: "e4590ed6-62b3-5102-beff-bad2292ab01c", - azpacr: "0", - e_exp: 262800, - name: "Mila Nikolova", - oid: "6467882c-fdfd-4354-a1ed-4e13f064be25", - preferred_username: "milan@contoso.com", - scp: "access_as_user", - sub: "XkjgWjdmaZ-_xDmhgN1BMP2vL2YOfeVxfPT_o8GRWaw", - tid: "fec4f964-8bc9-4fac-b972-1c1da35adbcd", - uti: "MICAQyhrH02ov54bCtIDAA", - ver: "2.0" -} -``` +> [!TIP] +> The most common failure codes are `installedappnotfound` (bot app not installed for the user) and `resourcematchfailed` (Token Exchange URL doesn't match the Application ID URI). For more information, see [SSO Troubleshooting](/microsoftteams/platform/teams-sdk/teams/user-authentication/troubleshooting-sso). ## Handle app user sign out -Use the following code snippet to handle the access token in case the app user signs out: +Call the `signout` method to remove the user's authentication token from the User Token service cache, effectively signing them out. The Teams SDK replaces the previous pattern of using `DialogContext`, `UserTokenClient`, and `CancelAllDialogsAsync` with a simple method call. # [C#](#tab/cs4) ```csharp - private async Task InterruptAsync(DialogContext innerDc, - CancellationToken cancellationToken = default(CancellationToken)) - { - if (innerDc.Context.Activity.Type == ActivityTypes.Message) - { - var text = innerDc.Context.Activity.Text.ToLowerInvariant(); - - // Allow logout anywhere in the command. - if (text.IndexOf("logout") >= 0) - { - // The UserTokenClient encapsulates the authentication processes. - var userTokenClient = innerDc.Context.TurnState.Get(); - await userTokenClient.SignOutUserAsync( - innerDc.Context.Activity.From.Id, - ConnectionName, - innerDc.Context.Activity.ChannelId, - cancellationToken - ).ConfigureAwait(false); - - await innerDc.Context.SendActivityAsync(MessageFactory.Text("You have been signed out."), cancellationToken); - return await innerDc.CancelAllDialogsAsync(cancellationToken); - } - } - - return null; - } +teams.OnMessage("/signout", async (context, cancellationToken) => +{ + if (!context.IsSignedIn) + { + await context.Send("You are not signed in.", cancellationToken); + return; + } + + await context.SignOut(cancellationToken); + await context.Send("You have been signed out.", cancellationToken); +}); ``` -# [JavaScript](#tab/js4) +# [TypeScript](#tab/ts4) -```JavaScript - async interrupt(innerDc) { - if (innerDc.context.activity.type === ActivityTypes.Message) { - const text = innerDc.context.activity.text.toLowerCase(); - if (text === 'logout') { - const userTokenClient = innerDc.context.turnState.get(innerDc.context.adapter.UserTokenClientKey); +```typescript +app.message('/signout', async ({ signout, send, isSignedIn }) => { + if (!isSignedIn) return; + await signout(); + await send('You have been signed out.'); +}); +``` - const { activity } = innerDc.context; - await userTokenClient.signOutUser(activity.from.id, this.connectionName, activity.channelId); +# [Python](#tab/py4) - await innerDc.context.sendActivity('You have been signed out.'); - return await innerDc.cancelAllDialogs(); - } - } - } +```python +@app.on_message +async def handle_signout(ctx: ActivityContext[MessageActivity]): + if "/signout" in ctx.activity.text.lower(): + if not ctx.is_signed_in: + await ctx.send("You are not signed in.") + return + + await ctx.sign_out() + await ctx.send("You have been signed out.") ``` --- -> [!div class="nextstepaction"] -> [I ran into an issue](https://github.com/MicrosoftDocs/msteams-docs/issues/new?template=Doc-Feedback.yaml&title=%5BI+ran+into+an+issue%5D+Handle+app+user+sign+out&&author=%40surbhigupta&pageUrl=https%3A%2F%2Flearn.microsoft.com%2Fen-us%2Fmicrosoftteams%2Fplatform%2Fbots%2Fhow-to%2Fauthentication%2Fbot-sso-code%3Ftabs%3Dcs1%252Ccs2%252Ccs3%252Ccs4%252Ccs5%26pivots%3Dmex-app%23handle-app-user-sign-out&contentSourceUrl=https%3A%2F%2Fgithub.com%2FMicrosoftDocs%2Fmsteams-docs%2Fblob%2Fmain%2Fmsteams-platform%2Fbots%2Fhow-to%2Fauthentication%2Fbot-sso-code.md&documentVersionIndependentId=039ff5cc-7243-ce4b-527e-c152755eeb72&platformId=915789b2-9617-01bb-fb21-d6789a634ed8&metadata=*%2BID%253A%2Be473e1f3-69f5-bcfa-bcab-54b098b59c80%2B%250A*%2BService%253A%2B%2A%2Amsteams%2A%2A) - ## Code sample -| **Sample name** | **Description** | **C#** | **Node.js** | -| --- | --- | --- | --- | -| Bot conversation SSO quick start | Quickly set up Teams bot with SSO for seamless user authentication for one-on-one and group chats. | [View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/bot-conversation-sso-quickstart/csharp_dotnetcore/BotConversationSsoQuickstart) | [View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/bot-conversation-sso-quickstart/js) | +| **Sample name** | **Description** | **C#** | **Node.js** | **Python** | +| --- | --- | --- | --- | --- | +| Bot auth quickstart | Demonstrates SSO authentication for Teams bots using the Teams SDK. | [View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/TeamsSDK/bot-auth-quickstart/dotnet/bot-auth-quickstart) | [View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/TeamsSDK/bot-auth-quickstart/nodejs/bot-auth-quickstart) | [View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/TeamsSDK/bot-auth-quickstart/python/bot-auth-quickstart) | ::: zone-end diff --git a/msteams-platform/includes/messaging-extensions/msgex-sso-code.md b/msteams-platform/includes/messaging-extensions/msgex-sso-code.md index aab5f4f1d87..1c52f53f8a0 100644 --- a/msteams-platform/includes/messaging-extensions/msgex-sso-code.md +++ b/msteams-platform/includes/messaging-extensions/msgex-sso-code.md @@ -1,13 +1,12 @@ -> [!NOTE] -> `OnTeamsMessagingExtensionQueryAsync` and `OnTeamsAppBasedLinkQueryAsync` from the `TeamsMessagingExtensionsSearchAuthConfigBot.cs` file are the only SSO handlers that are supported. Other SSO handlers aren't supported. +The Teams SDK handles the SSO token exchange and OAuth flow automatically. You configure an OAuth connection name on the `App` object, then call `signin()` inside your message extension handlers. The SDK manages the underlying `signin/verifyState` and `signin/tokenExchange` invoke activities for you. This section covers: 1. [Update development environment variables](#update-development-environment-variables) -1. [Add code to request a token](#add-code-to-request-a-token) -1. [Add code to receive the token](#add-code-to-receive-the-token) -1. [Add token to Bot Framework Token Store](#add-token-to-bot-framework-token-store) -1. [Handle app user log out](#handle-app-user-log-out) +1. [Configure the OAuth connection](#configure-the-oauth-connection) +1. [Implement message extension handlers with authentication](#implement-message-extension-handlers-with-authentication) +1. [Handle sign-in events and failures](#handle-sign-in-events-and-failures) +1. [Handle sign out](#handle-sign-out) ## Update development environment variables @@ -16,211 +15,83 @@ You've configured client secret and OAuth connection setting for the app in Micr To update the development environment variables: 1. Open the app project. -1. Open the `./env` file for your project. -1. Update the following variables: +1. Open the configuration file for your project. +1. Update the environment variables as shown in the following tabs. +1. Save the file. - - For `MicrosoftAppId`, update the Bot registration ID from Microsoft Entra ID. - - For `MicrosoftAppPassword`, update the Bot registration client secret. - - For `ConnectionName`, update the name of the OAuth connection you configured in Microsoft Entra ID. - - For `MicrosoftAppTenantId`, update the tenant ID. +# [C#](#tab/cs1) -1. Save the file. +Update `appsettings.json` (or use environment variables in `launchSettings.json`): + +- `Teams:ClientID` – The Application (client) ID from your Microsoft Entra app registration. +- `Teams:ClientSecret` – The client secret you created. +- `Teams:TenantId` – Your Directory (tenant) ID. +- `Teams:ConnectionName` – The name of the OAuth connection you configured in Azure Bot Service. + +# [TypeScript](#tab/ts1) + +Update the `.env` file: -You've now configured the required environment variables for your bot app and SSO. Next, add the code for handling tokens. +- `CLIENT_ID` – The Application (client) ID from your Microsoft Entra app registration. +- `CLIENT_SECRET` – The client secret you created. +- `TENANT_ID` – Your Directory (tenant) ID. +- `CONNECTION_NAME` – The name of the OAuth connection you configured in Azure Bot Service. + +# [Python](#tab/py1) + +Update the `.env` file: + +- `CLIENT_ID` – The Application (client) ID from your Microsoft Entra app registration. +- `CLIENT_SECRET` – The client secret you created. +- `TENANT_ID` – Your Directory (tenant) ID. +- `CONNECTION_NAME` – The name of the OAuth connection you configured in Azure Bot Service. + +--- -> [!div class="nextstepaction"] -> [I ran into an issue](https://github.com/MicrosoftDocs/msteams-docs/issues/new?template=Doc-Feedback.yaml&title=%5BI+ran+into+an+issue%5D+Update+development+environment+variables&&author=%40surbhigupta&pageUrl=https%3A%2F%2Flearn.microsoft.com%2Fen-us%2Fmicrosoftteams%2Fplatform%2Fbots%2Fhow-to%2Fauthentication%2Fbot-sso-code%3Ftabs%3Dcs1%252Ccs2%252Ccs3%252Ccs4%252Ccs5%26pivots&contentSourceUrl=https%3A%2F%2Fgithub.com%2FMicrosoftDocs%2Fmsteams-docs%2Fblob%2Fmain%2Fmsteams-platform%2Fbots%2Fhow-to%2Fauthentication%2Fbot-sso-code.md&documentVersionIndependentId=039ff5cc-7243-ce4b-527e-c152755eeb72&platformId=915789b2-9617-01bb-fb21-d6789a634ed8&metadata=*%2BID%253A%2Be473e1f3-69f5-bcfa-bcab-54b098b59c80%2B%250A*%2BService%253A%2B%2A%2Amsteams%2A%2A) +You've now configured the required environment variables for your message extension app and SSO. Next, configure the OAuth connection in your app code. -## Add code to request a token +## Configure the OAuth connection -The request to get the token is a POST message request using the existing message schema. It's included in the attachments of an OAuthCard. The schema for the OAuthCard class is defined in [Microsoft Bot Schema 4.0](/dotnet/api/microsoft.bot.schema.oauthcard?view=botbuilder-dotnet-stable&preserve-view=true). Teams refreshes the token if the `TokenExchangeResource` property is populated on the card. For the Teams channel, only the `Id` property, which uniquely identifies a token request, is honored. +The Teams SDK provides built-in OAuth support. You configure the OAuth connection name when creating the `App` object, and the SDK automatically handles token exchange and SSO for all handler routes. ->[!NOTE] -> The Microsoft Bot Framework `OAuthPrompt` or the `MultiProviderAuthDialog` is supported for SSO authentication. - -To update your app's code: - -1. Add code snippet for `TeamsSSOTokenExchangeMiddleware`. - - # [C#](#tab/cs1) - - Add the following code snippet to `AdapterWithErrorHandler.cs` (or the equivalent class in your app's code): - - ```csharp - base.Use(new TeamsSSOTokenExchangeMiddleware(storage, configuration["ConnectionName"])); - ``` - - # [JavaScript](#tab/js1) - - Add the following code snippet to `index.js` (or the equivalent class in your app's code): - - ```JavaScript - const {TeamsSSOTokenExchangeMiddleware} = require('botbuilder'); - const tokenExchangeMiddleware = new TeamsSSOTokenExchangeMiddleware(memoryStorage, env.connectionName); - adapter.use(tokenExchangeMiddleware); - ``` - - --- - - >[!NOTE] - > You might receive multiple responses for a given request if the user has multiple active endpoints. You must eliminate all duplicate or redundant responses with the token. For more information about signin/tokenExchange, see [TeamsSSOTokenExchangeMiddleware Class](/python/api/botbuilder-core/botbuilder.core.teams.teams_sso_token_exchange_middleware.teamsssotokenexchangemiddleware?view=botbuilder-py-latest&preserve-view=true#remarks). - -1. Use the following code snippet for requesting a token. - - # [C#](#tab/cs2) - - After you add the `AdapterWithErrorHandler.cs`, the following code must appear: - - ```csharp - public class AdapterWithErrorHandler : CloudAdapter - { - public AdapterWithErrorHandler( - IConfiguration configuration, - IHttpClientFactory httpClientFactory, - ILogger logger, - IStorage storage, - ConversationState conversationState) - : base(configuration, httpClientFactory, logger) - { - base.Use(new TeamsSSOTokenExchangeMiddleware(storage, configuration["ConnectionName"])); - - OnTurnError = async (turnContext, exception) => - { - // Log any leaked exception from the application. - // NOTE: In production environment, you must consider logging this to - // Azure Application Insights. Visit https://learn.microsoft.com/en-us/azure/bot-service/bot-builder-telemetry?view=azure-bot-service-4.0&tabs=csharp to see how - // to add telemetry capture to your bot. - logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}"); - - // Send a message to the user. - await turnContext.SendActivityAsync("The bot encountered an error or bug."); - await turnContext.SendActivityAsync("To continue to run this bot, please fix the bot source code."); - - if (conversationState != null) - { - try - { - // Delete the conversationState for the current conversation to prevent the - // bot from getting stuck in an error-loop caused by being in a bad state. - // ConversationState must be thought of as similar to "cookie-state" in a Web pages. - await conversationState.DeleteAsync(turnContext); - } - catch (Exception e) - { - logger.LogError(e, $"Exception caught on attempting to Delete ConversationState : {e.Message}"); - } - } - - // Send a trace activity, which will be displayed in the Bot Framework Emulator. - await turnContext.TraceActivityAsync( - "OnTurnError Trace", - exception.Message, - "https://www.botframework.com/schemas/error", - "TurnError"); - }; - } - } - ``` - - # [JavaScript](#tab/js2) - - After you add the code to `index.js`, the following code must appear: - - ```JavaScript - // index.js is used to setup and configure your bot. - - // Import required packages. - const path = require('path'); - - // Read botFilePath and botFileSecret from .env file. - const ENV_FILE = path.join(__dirname, '.env'); - require('dotenv').config({ path: ENV_FILE }); - - const express = require("express"); - - // Import required bot services. - // See https://learn.microsoft.com/en-us/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0 to learn more about the different parts of a bot. - const { - CloudAdapter, - ConversationState, - MemoryStorage, - UserState, - ConfigurationBotFrameworkAuthentication, - TeamsSSOTokenExchangeMiddleware - } = require('botbuilder'); - - const { TeamsBot } = require('./bots/teamsBot'); - const { MainDialog } = require('./dialogs/mainDialog'); - const { env } = require('process'); - - const botFrameworkAuthentication = new ConfigurationBotFrameworkAuthentication(process.env); - - var conname = env.connectionName; - - console.log(`\n${ conname } is the con name`); - - // Create adapter. - // See https://learn.microsoft.com/en-us/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0 to learn more about how bots work. - const adapter = new CloudAdapter(botFrameworkAuthentication); - const memoryStorage = new MemoryStorage(); - const tokenExchangeMiddleware = new TeamsSSOTokenExchangeMiddleware(memoryStorage, env.connectionName); - - adapter.use(tokenExchangeMiddleware); - adapter.onTurnError = async (context, error) => { - // This check writes out errors to console log .vs. app insights. - // NOTE: In production environment, you must consider logging this to Azure application insights. See https://learn.microsoft.com/en-us/azure/bot-service/bot-builder-telemetry?view=azure-bot-service-4.0&tabs=csharp for telemetry configuration instructions. - console.error(`\n [onTurnError] unhandled error: ${ error }`); - - // Send a trace activity, which will be displayed in Bot Framework Emulator. - await context.sendTraceActivity( - 'OnTurnError Trace', - `${ error }`, - 'https://www.botframework.com/schemas/error', - 'TurnError' - ); - - // Send a message to the user. - await context.sendActivity('The bot encountered an error or bug.'); - await context.sendActivity('To continue to run this bot, please fix the bot source code.'); - // Clear out state. - await conversationState.delete(context); - }; - - // Define the state store for your bot. - // See https://learn.microsoft.com/en-us/azure/bot-service/bot-builder-howto-v4-state?view=azure-bot-service-4.0&branch=live&tabs=csharp to learn more about using MemoryStorage. - // A bot requires a state storage system to persist the dialog and user state between messages. - //const memoryStorage = new MemoryStorage(); - - // Create conversation and user state with in-memory storage provider. - const conversationState = new ConversationState(memoryStorage); - const userState = new UserState(memoryStorage); - - // Create the main dialog. - const dialog = new MainDialog(); - // Create the bot that will handle incoming messages. - const bot = new TeamsBot(conversationState, userState, dialog); - - // Create express application. - const expressApp = express(); - expressApp.use(express.json()); - - const server = expressApp.listen(process.env.port || process.env.PORT || 3978, () => { - console.log(`\n${ expressApp.name } listening to`, server.address()); - console.log('\nGet Bot Framework Emulator: https://aka.ms/botframework-emulator'); - console.log('\nTo talk to your bot, open the emulator select "Open Bot"'); - }); - - // Listen for incoming requests. - expressApp.post('/api/messages', async (req, res) => { - // Route received a request to adapter for processing. - await adapter.process(req, res, (context) => bot.run(context)); - }); - - ``` - -> [!div class="nextstepaction"] -> [I ran into an issue](https://github.com/MicrosoftDocs/msteams-docs/issues/new?template=Doc-Feedback.yaml&title=%5BI+ran+into+an+issue%5D+Add+code+to+request+a+token&&author=%40surbhigupta&pageUrl=https%3A%2F%2Flearn.microsoft.com%2Fen-us%2Fmicrosoftteams%2Fplatform%2Fbots%2Fhow-to%2Fauthentication%2Fbot-sso-code&contentSourceUrl=https%3A%2F%2Fgithub.com%2FMicrosoftDocs%2Fmsteams-docs%2Fblob%2Fmain%2Fmsteams-platform%2Fbots%2Fhow-to%2Fauthentication%2Fbot-sso-code.md&documentVersionIndependentId=039ff5cc-7243-ce4b-527e-c152755eeb72&platformId=915789b2-9617-01bb-fb21-d6789a634ed8&metadata=*%2BID%253A%2Be473e1f3-69f5-bcfa-bcab-54b098b59c80%2B%250A*%2BService%253A%2B%2A%2Amsteams%2A%2A) +# [C#](#tab/cs2) +```csharp +var builder = WebApplication.CreateBuilder(args); + +var connectionName = builder.Configuration["Teams:ConnectionName"]; + +builder.AddTeams(App.Builder().AddOAuth(connectionName)); + +var app = builder.Build(); +var teams = app.UseTeams(); +``` + +# [TypeScript](#tab/ts2) + +```typescript +import { App } from '@microsoft/teams.apps'; + +const app = new App({ + oauth: { + defaultConnectionName: process.env.CONNECTION_NAME || 'graph', // Use the same name as your Azure Bot OAuth connection + }, +}); +``` + +# [Python](#tab/py2) + +```python +from microsoft_teams.apps import App, AppOptions + +app = App( + **AppOptions( + default_connection_name=os.getenv("CONNECTION_NAME", "graph"), # Use the same name as your Azure Bot OAuth connection + ) +) +``` + +--- ### Consent dialog for getting access token @@ -241,362 +112,267 @@ The consent dialog that appears is for open-id scopes defined in Microsoft Entra If you encounter any errors, see [Troubleshoot SSO authentication in Teams](~/tabs/how-to/authentication/tab-sso-troubleshooting.md). -> [!div class="nextstepaction"] -> [I ran into an issue](https://github.com/MicrosoftDocs/msteams-docs/issues/new?template=Doc-Feedback.yaml&title=%5BI+ran+into+an+issue%5D+Consent+dialog+for+getting+access+token&&author=%40surbhigupta&pageUrl=https%3A%2F%2Flearn.microsoft.com%2Fen-us%2Fmicrosoftteams%2Fplatform%2Fbots%2Fhow-to%2Fauthentication%2Fbot-sso-code%3Ftabs%3Dcs1%252Ccs2%252Ccs3%252Ccs4%252Ccs5%26pivots%3Dmex-app%23consent-dialog-for-getting-access-token&contentSourceUrl=https%3A%2F%2Fgithub.com%2FMicrosoftDocs%2Fmsteams-docs%2Fblob%2Fmain%2Fmsteams-platform%2Fbots%2Fhow-to%2Fauthentication%2Fbot-sso-code.md&documentVersionIndependentId=039ff5cc-7243-ce4b-527e-c152755eeb72&platformId=915789b2-9617-01bb-fb21-d6789a634ed8&metadata=*%2BID%253A%2Be473e1f3-69f5-bcfa-bcab-54b098b59c80%2B%250A*%2BService%253A%2B%2A%2Amsteams%2A%2A) +## Implement message extension handlers with authentication -## Add code to receive the token +With the Teams SDK, you implement message extension handlers using event-driven patterns. The SDK provides the auth context (such as `signin`, `isSignedIn`, and `userGraph`) directly in the handler, so there's no need for manual token exchange or middleware. -The response with the token is sent through an invoke activity with the same schema as other invoke activities that the bots receive today. The only difference is the invoke name, -sign in/tokenExchange, and the **value** field. The **value** field contains the **Id**, a string of the initial request to get the token and the **token** field, a string value including the token. +### Search command handler -Use the following code snippet example to invoke response: +The following code shows how to handle a search query in a message extension, with SSO authentication to access Microsoft Graph on behalf of the user: # [C#](#tab/cs3) +In C#, register an inline handler on the `teams` pipeline using `teams.OnQuery(...)`. The context provides the activity and cancellation token: + ```csharp -public MainDialog(IConfiguration configuration, ILogger logger) - : base(nameof(MainDialog), configuration["ConnectionName"]) - { - AddDialog(new OAuthPrompt( - nameof(OAuthPrompt), - new OAuthPromptSettings - { - ConnectionName = ConnectionName, - Text = "Please Sign In", - Title = "Sign In", - Timeout = 300000, // User has 5 minutes to login (1000 * 60 * 5) - EndOnInvalidMessage = true - })); - - AddDialog(new ConfirmPrompt(nameof(ConfirmPrompt))); - - AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[] - { - PromptStepAsync, - LoginStepAsync, - })); - - // The initial child Dialog to run. - InitialDialogId = nameof(WaterfallDialog); - } +using Microsoft.Teams.Api.MessageExtensions; +using Microsoft.Teams.Cards; +using Microsoft.Teams.Api.Cards; +// ... -private async Task PromptStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) - { - return await stepContext.BeginDialogAsync(nameof(OAuthPrompt), null, cancellationToken); - } +teams.OnQuery(async (ctx) => +{ + var commandId = ctx.Activity.Value.CommandId; + var query = ctx.Activity.Value.Parameters? + .FirstOrDefault()?.Value?.ToString() ?? ""; -private async Task LoginStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + Console.WriteLine($"Query: command={commandId}, query={query}"); + + if (commandId == "searchQuery") + { + return CreateSearchResults(query); + } + + return new Response + { + ComposeExtension = new Result { - - var tokenResponse = (TokenResponse)stepContext.Result; - if (tokenResponse?.Token != null) - { - var token = tokenResponse.Token; - - // On successful login, the token contains sign in token. - } - else - { - await stepContext.Context.SendActivityAsync(MessageFactory.Text("Login was not successful please try again."), cancellationToken); - } - - return await stepContext.EndDialogAsync(cancellationToken: cancellationToken); + Type = ResultType.Result, + AttachmentLayout = Microsoft.Teams.Api.Attachment.Layout.List, + Attachments = new List() } + }; +}); ``` -# [JavaScript](#tab/js3) - - ```JavaScript - class MainDialog { - - this.addDialog(new OAuthPrompt(OAUTH_PROMPT, { - connectionName: process.env.connectionName, - text: 'Please Sign In', - title: 'Sign In', - timeout: 300000 - })); - - this.addDialog(new ConfirmPrompt(CONFIRM_PROMPT)); - - this.addDialog(new WaterfallDialog(MAIN_WATERFALL_DIALOG, [ - this.promptStep.bind(this), - this.loginStep.bind(this), - ])); - - this.initialDialogId = MAIN_WATERFALL_DIALOG; - - } - - async promptStep(stepContext) { - try { - return await stepContext.beginDialog(OAUTH_PROMPT); - } catch (err) { - console.error(err); - } - } - - async loginStep(stepContext) { - // Get the token from the previous step. Note that we could also have gotten the - // token directly from the prompt itself. There is an example of this in the next method. - const tokenResponse = stepContext.result; - if (!tokenResponse || !tokenResponse.token) { - await stepContext.context.sendActivity('Login was not successful please try again.'); - } else { - const token = tokenResponse.token; - // On successful login, the token contains sign in token. - } - return await stepContext.endDialog(); - } - ``` - - --- - -> [!NOTE] -> The code snippets use the Waterfall Dialog bot. For more information about Waterfall Dialog, see [About component and waterfall dialogs](/azure/bot-service/bot-builder-concept-waterfall-dialogs?view=azure-bot-service-4.0&preserve-view=true). - -You receive the token in `OnTeamsMessagingExtensionQueryAsync` handler in the `turnContext.Activity.Value` payload or in the `OnTeamsAppBasedLinkQueryAsync`, depending on which scenario you're enabling SSO for. +# [TypeScript](#tab/ts3) + +```typescript +import { cardAttachment } from '@microsoft/teams.api'; +import { App } from '@microsoft/teams.apps'; +import { AdaptiveCard, TextBlock } from '@microsoft/teams.cards'; + +app.on('message.ext.query', async ({ activity, signin, isSignedIn }) => { + // Trigger sign-in if user is not authenticated + if (!isSignedIn) { + await signin(); + return { status: 200 }; + } + + const { commandId } = activity.value; + const searchQuery = activity.value.parameters?.[0]?.value ?? ''; + + if (commandId === 'searchQuery') { + const cards = await createSearchResults(searchQuery); + const attachments = cards.map(({ card, thumbnail }) => ({ + ...cardAttachment('adaptive', card), + preview: cardAttachment('thumbnail', thumbnail), + })); + + return { + composeExtension: { + type: 'result', + attachmentLayout: 'list', + attachments, + }, + }; + } + + return { status: 400 }; +}); +``` -```json -JObject valueObject=JObject.FromObject(turnContext.Activity.Value); -if(valueObject["authentication"] !=null) - { - JObject authenticationObject=JObject.FromObject(valueObject["authentication"]); - if(authenticationObject["token"] !=null) - } +# [Python](#tab/py3) + +```python +from microsoft_teams.api import ( + AdaptiveCardAttachment, + MessageExtensionQueryInvokeActivity, + ThumbnailCardAttachment, + card_attachment, + AttachmentLayout, + MessagingExtensionAttachment, + MessagingExtensionInvokeResponse, + MessagingExtensionResult, + MessagingExtensionResultType, + InvokeResponse, +) +from microsoft_teams.apps import ActivityContext + +@app.on_message_ext_query +async def handle_message_ext_query( + ctx: ActivityContext[MessageExtensionQueryInvokeActivity], +): + # Trigger sign-in if user is not authenticated + if not ctx.is_signed_in: + await ctx.sign_in() + return InvokeResponse[MessagingExtensionInvokeResponse](status=200) + + command_id = ctx.activity.value.command_id + search_query = "" + if ctx.activity.value.parameters and len(ctx.activity.value.parameters) > 0: + search_query = ctx.activity.value.parameters[0].value or "" + + if command_id == "searchQuery": + cards = await create_search_results(search_query) + attachments = [] + for card_data in cards: + main = card_attachment(AdaptiveCardAttachment(content=card_data["card"])) + preview = card_attachment(ThumbnailCardAttachment(content=card_data["thumbnail"])) + attachments.append( + MessagingExtensionAttachment( + content_type=main.content_type, content=main.content, preview=preview + ) + ) + + result = MessagingExtensionResult( + type=MessagingExtensionResultType.RESULT, + attachment_layout=AttachmentLayout.LIST, + attachments=attachments, + ) + return MessagingExtensionInvokeResponse(compose_extension=result) + + return InvokeResponse[MessagingExtensionInvokeResponse](status=400) ``` -> [!div class="nextstepaction"] -> [I ran into an issue](https://github.com/MicrosoftDocs/msteams-docs/issues/new?template=Doc-Feedback.yaml&title=%5BI+ran+into+an+issue%5D+Add+code+to+receive+the+token&&author=%40surbhigupta&pageUrl=https%3A%2F%2Flearn.microsoft.com%2Fen-us%2Fmicrosoftteams%2Fplatform%2Fbots%2Fhow-to%2Fauthentication%2Fbot-sso-code%3Ftabs%3Dcs1%252Ccs2%252Ccs3%252Ccs4%252Ccs5%26pivots%3Dmex-app%23add-code-to-receive-the-token&contentSourceUrl=https%3A%2F%2Fgithub.com%2FMicrosoftDocs%2Fmsteams-docs%2Fblob%2Fmain%2Fmsteams-platform%2Fbots%2Fhow-to%2Fauthentication%2Fbot-sso-code.md&documentVersionIndependentId=039ff5cc-7243-ce4b-527e-c152755eeb72&platformId=915789b2-9617-01bb-fb21-d6789a634ed8&metadata=*%2BID%253A%2Be473e1f3-69f5-bcfa-bcab-54b098b59c80%2B%250A*%2BService%253A%2B%2A%2Amsteams%2A%2A) -### Validate the access token -Web APIs on your server must decode the access token and verify if it's sent from the client. +--- -> [!NOTE] -> If you use Bot Framework, it handles the access token validation. If you don't use Bot Framework, follow the guidelines in this section. +## Handle sign-in events and failures -For more information about validating access token, see [Validate tokens](/azure/active-directory/develop/access-tokens#validate-tokens). +The Teams SDK lets you subscribe to sign-in events that fire when the OAuth flow completes successfully, as well as sign-in failures when the SSO token exchange fails. -There are a number of libraries available that can handle JWT validation. Basic validation includes: +### Subscribe to the sign-in event -- Checking that the token is well-formed. -- Checking that the token was issued by the intended authority. -- Checking that the token is targeted to the web API. +# [C#](#tab/cs5) + +```csharp +teams.OnSignIn(async (_, @event) => +{ + var context = @event.Context; + await context.Send( + "Successfully signed in!"); +}); +``` -Keep in mind the following guidelines when validating the token: +# [TypeScript](#tab/ts5) -- Valid SSO tokens are issued by Microsoft Entra ID. The `iss` claim in the token must start with this value. -- The token's `aud1` parameter is set to the app ID generated during Microsoft Entra app registration. -- The token's `scp` parameter is set to `access_as_user`. +```typescript +app.event('signin', async ({ send }) => { + await send('Successfully signed in!'); +}); +``` -#### Example access token +# [Python](#tab/py5) -The following code snippet is a typical decoded payload of an access token: +```python +from microsoft_teams.apps import SignInEvent -```javascript -{ - aud: "2c3caa80-93f9-425e-8b85-0745f50c0d24", - iss: "https://login.microsoftonline.com/fec4f964-8bc9-4fac-b972-1c1da35adbcd/v2.0", - iat: 1521143967, - nbf: 1521143967, - exp: 1521147867, - aio: "ATQAy/8GAAAA0agfnU4DTJUlEqGLisMtBk5q6z+6DB+sgiRjB/Ni73q83y0B86yBHU/WFJnlMQJ8", - azp: "e4590ed6-62b3-5102-beff-bad2292ab01c", - azpacr: "0", - e_exp: 262800, - name: "Mila Nikolova", - oid: "6467882c-fdfd-4354-a1ed-4e13f064be25", - preferred_username: "milan@contoso.com", - scp: "access_as_user", - sub: "XkjgWjdmaZ-_xDmhgN1BMP2vL2YOfeVxfPT_o8GRWaw", - tid: "fec4f964-8bc9-4fac-b972-1c1da35adbcd", - uti: "MICAQyhrH02ov54bCtIDAA", - ver: "2.0" -} +@app.event("sign_in") +async def handle_sign_in(event: SignInEvent): + await event.activity_ctx.send("Successfully signed in!") ``` -## Add token to Bot Framework Token Store +--- -If you're using the OAuth connection, you must update or add the token in the Bot Framework Token store. Add the following code snippet example to `TeamsMessagingExtensionsSearchAuthConfigBot.cs` (or the equivalent file in your app's code) for updating or adding the token in the store: +### Handle sign-in failures -> [!NOTE] -> You can find the sample `TeamsMessagingExtensionsSearchAuthConfigBot.cs` in [Tab, Bot, and Message Extension (ME) SSO](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/TeamsJS/app-sso/csharp/App%20SSO%20Sample/Bots). +When using SSO, if the token exchange fails, Teams sends a `signin/failure` invoke activity to your app. The SDK includes a built-in default handler that logs a warning. You can optionally register your own handler to customize the behavior: -# [C#](#tab/cs4) +# [C#](#tab/cs6) ```csharp -protected override async Task OnInvokeActivityAsync(ITurnContext 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 TokenIsExchangeable(ITurnContext turnContext, CancellationToken cancellationToken) - { - TokenResponse tokenExchangeResponse = null; - try - { - JObject valueObject = JObject.FromObject(turnContext.Activity.Value); - var tokenExchangeRequest = - ((JObject)valueObject["authentication"])?.ToObject(); - var userTokenClient = turnContext.TurnState.Get(); - tokenExchangeResponse = await userTokenClient.ExchangeTokenAsync( - turnContext.Activity.From.Id, - _connectionName, - turnContext.Activity.ChannelId, - 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; - } +teams.OnSignInFailure(async (_, @event) => +{ + Console.WriteLine($"Sign-in failed: {@event.Error.Code} - {@event.Error.Message}"); + await @event.Context.Send("Sign-in failed."); +}); ``` -# [JavaScript](#tab/js4) - -```JavaScript - -async onInvokeActivity(context) { - console.log('onInvoke, ' + context.activity.name); - const valueObj = context.activity.value; - if (valueObj.authentication) { - const authObj = valueObj.authentication; - if (authObj.token) { - // If the token is NOT exchangeable, then do NOT deduplicate requests. - if (await this.tokenIsExchangeable(context)) { - return await super.onInvokeActivity(context); - } else { - const response = { - status: 412 - }; - return response; - } - } - } - return await super.onInvokeActivity(context); -} - -async tokenIsExchangeable(context) { - let tokenExchangeResponse = null; - try { - const valueObj = context.activity.value; - const tokenExchangeRequest = valueObj.authentication; - tokenExchangeResponse = await context.adapter.exchangeToken(context, - process.env.connectionName, - context.activity.from.id, - { token: tokenExchangeRequest.token }); - } catch (err) { - console.log('tokenExchange error: ' + err); - // Ignore Exceptions - // If token exchange failed for any reason, tokenExchangeResponse above stays null , and hence we send back a failure invoke response to the caller. - } - if (!tokenExchangeResponse || !tokenExchangeResponse.token) { - return false; - } - return true; -} +# [TypeScript](#tab/ts6) + +```typescript +app.on('signin.failure', async ({ activity, send }) => { + const { code, message } = activity.value; + console.log(`Sign-in failed: ${code} - ${message}`); + await send('Sign-in failed.'); +}); ``` ---- +# [Python](#tab/py6) -> [!div class="nextstepaction"] -> [I ran into an issue](https://github.com/MicrosoftDocs/msteams-docs/issues/new?template=Doc-Feedback.yaml&title=%5BI+ran+into+an+issue%5D+Add+token+to+Bot+Framework+Token+Store&&author=%40surbhigupta&pageUrl=https%3A%2F%2Flearn.microsoft.com%2Fen-us%2Fmicrosoftteams%2Fplatform%2Fbots%2Fhow-to%2Fauthentication%2Fbot-sso-code%3Ftabs%3Dcs1%252Ccs2&contentSourceUrl=https%3A%2F%2Fgithub.com%2FMicrosoftDocs%2Fmsteams-docs%2Fblob%2Fmain%2Fmsteams-platform%2Fbots%2Fhow-to%2Fauthentication%2Fbot-sso-code.md&documentVersionIndependentId=039ff5cc-7243-ce4b-527e-c152755eeb72&platformId=915789b2-9617-01bb-fb21-d6789a634ed8&metadata=*%2BID%253A%2Be473e1f3-69f5-bcfa-bcab-54b098b59c80%2B%250A*%2BService%253A%2B%2A%2Amsteams%2A%2A) +```python +@app.on_signin_failure() +async def handle_signin_failure(ctx): + failure = ctx.activity.value + print(f"Sign-in failed: {failure.code} - {failure.message}") + await ctx.send("Sign-in failed.") +``` -## Handle app user log out +--- -Use the following code snippet to handle the access token in case the app user logs out: +## Handle sign out -# [C#](#tab/cs5) +Use the following code to sign out the user and clear the token from the User Token Service cache: + +# [C#](#tab/cs7) ```csharp - private async Task InterruptAsync(DialogContext innerDc, - CancellationToken cancellationToken = default(CancellationToken)) - { - if (innerDc.Context.Activity.Type == ActivityTypes.Message) - { - var text = innerDc.Context.Activity.Text.ToLowerInvariant(); - - // Allow logout anywhere in the command. - if (text.IndexOf("logout") >= 0) - { - // The UserTokenClient encapsulates the authentication processes. - var userTokenClient = innerDc.Context.TurnState.Get(); - await userTokenClient.SignOutUserAsync( - innerDc.Context.Activity.From.Id, - ConnectionName, - innerDc.Context.Activity.ChannelId, - cancellationToken - ).ConfigureAwait(false); - - await innerDc.Context.SendActivityAsync(MessageFactory.Text("You have been signed out."), cancellationToken); - return await innerDc.CancelAllDialogsAsync(cancellationToken); - } - } - - return null; - } +teams.OnMessage("signout", async context => +{ + if (!context.IsSignedIn) + { + await context.Send("You are not signed in."); + return; + } + + await context.SignOut(); + await context.Send("You have been signed out."); +}); ``` -# [JavaScript](#tab/js5) +# [TypeScript](#tab/ts7) -```JavaScript - async interrupt(innerDc) { - if (innerDc.context.activity.type === ActivityTypes.Message) { - const text = innerDc.context.activity.text.toLowerCase(); - if (text === 'logout') { - const userTokenClient = innerDc.context.turnState.get(innerDc.context.adapter.UserTokenClientKey); +```typescript +app.message('/signout', async ({ send, signout, isSignedIn }) => { + if (!isSignedIn) { + await send('You are not signed in.'); + return; + } - const { activity } = innerDc.context; - await userTokenClient.signOutUser(activity.from.id, this.connectionName, activity.channelId); + await signout(); + await send('You have been signed out.'); +}); +``` - await innerDc.context.sendActivity('You have been signed out.'); - return await innerDc.cancelAllDialogs(); - } - } - } +# [Python](#tab/py7) + +```python +@app.on_message_pattern("signout") +async def handle_signout(ctx: ActivityContext[MessageActivity]): + if not ctx.is_signed_in: + await ctx.send("You are not signed in.") + return + + await ctx.sign_out() + await ctx.send("You have been signed out.") ``` --- -> [!div class="nextstepaction"] -> [I ran into an issue](https://github.com/MicrosoftDocs/msteams-docs/issues/new?template=Doc-Feedback.yaml&title=%5BI+ran+into+an+issue%5D+Handle+app+user+log+out&&author=%40surbhigupta&pageUrl=https%3A%2F%2Flearn.microsoft.com%2Fen-us%2Fmicrosoftteams%2Fplatform%2Fbots%2Fhow-to%2Fauthentication%2Fbot-sso-code%3Ftabs%3Dcs1%252Ccs2%252Ccs3%252Ccs4%252Ccs5%26pivots%3Dmex-app%23handle-app-user-log-out&contentSourceUrl=https%3A%2F%2Fgithub.com%2FMicrosoftDocs%2Fmsteams-docs%2Fblob%2Fmain%2Fmsteams-platform%2Fbots%2Fhow-to%2Fauthentication%2Fbot-sso-code.md&documentVersionIndependentId=039ff5cc-7243-ce4b-527e-c152755eeb72&platformId=915789b2-9617-01bb-fb21-d6789a634ed8&metadata=*%2BID%253A%2Be473e1f3-69f5-bcfa-bcab-54b098b59c80%2B%250A*%2BService%253A%2B%2A%2Amsteams%2A%2A) - ## Code sample -This section provides bot authentication v3 SDK sample. - -| **Sample name** | **Description** | **.NET** | **Node.js** | **Python** | **Manifest** | -|---------------|------------|------------|-------------|---------------|---------------| -| Bot authentication | This sample app demonstrate how an Bot can use Teams authentication. | [View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/bot-teams-authentication/csharp) | [View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/bot-conversation-sso-quickstart/js) | [View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/bot-teams-authentication/python) |[View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/bot-teams-authentication/csharp/demo-manifest/bot-teams-authentication.zip)| -| Tab, bot, and Message extension (ME) SSO | This sample app demonstrates Teams SSO integration for Tab, Bot, and Messaging Extension, using C# and Microsoft Entra ID for secure authentication. | [View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/TeamsJS/app-sso/csharp) | [View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/TeamsJS/app-sso/nodejs) | NA |[View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/TeamsJS/app-sso/csharp/demo-manifest/App-SSO.zip)| -|Tab, bot, and Message extension | This sample showcases Microsoft Entra ID and Facebook authentication across bots, tabs, and messaging extensions in Microsoft Teams. | [View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/app-complete-auth/csharp) | [View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/app-complete-auth/nodejs) | NA |[View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/app-complete-auth/csharp/demo-manifest/App-Complete-Auth.zip)| +| **Sample name** | **Description** | **.NET** | **Node.js** | **Python** | +|---|---|---|---|---| +| Bot auth quickstart | Demonstrates SSO authentication for Teams bots using the Teams SDK. | [View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/bot-auth-quickstart/dotnet/bot-auth-quickstart) | [View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/bot-auth-quickstart/nodejs/bot-auth-quickstart) | [View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/bot-auth-quickstart/python/bot-auth-quickstart) | +| Bot message extensions | Demonstrates search-based messaging extensions with the Teams SDK. | [View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/bot-message-extensions/dotnet/bot-message-extensions) | [View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/bot-message-extensions/nodejs/bot-message-extensions) | [View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/bot-message-extensions/python/bot-message-extensions) | From 532f37a8c9e55fe72f0e1a79788bfd00fb88f955 Mon Sep 17 00:00:00 2001 From: v-nikitach Date: Mon, 25 May 2026 15:07:04 +0530 Subject: [PATCH 2/7] Update sso-adaptive-cards-universal-action.md --- .../sso-adaptive-cards-universal-action.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/msteams-platform/task-modules-and-cards/cards/Universal-actions-for-adaptive-cards/sso-adaptive-cards-universal-action.md b/msteams-platform/task-modules-and-cards/cards/Universal-actions-for-adaptive-cards/sso-adaptive-cards-universal-action.md index eca510bf8d3..2865c7e673f 100644 --- a/msteams-platform/task-modules-and-cards/cards/Universal-actions-for-adaptive-cards/sso-adaptive-cards-universal-action.md +++ b/msteams-platform/task-modules-and-cards/cards/Universal-actions-for-adaptive-cards/sso-adaptive-cards-universal-action.md @@ -3,7 +3,7 @@ title: SSO for Adaptive Card Universal Actions description: Learn how to enable SSO for Adaptive Cards Universal Actions, add code to handle access token and receive token, and consent dialog to get access token. ms.topic: conceptual ms.localizationpriority: medium -ms.date: 02/06/2023 +ms.date: 05/25/2026 --- # Add code to enable SSO for Adaptive Cards Universal Actions @@ -118,13 +118,13 @@ When the app user selects **View and accept**, the existing Microsoft Entra perm 1. The bot returns a non-error response to the Teams client using either a card or message. > [!NOTE] -> To handle the access token in case the app user logs out, see [handle app user log out](../../../bots/how-to/authentication/bot-sso-code.md#handle-app-user-log-out). +> To handle the access token in case the app user logs out, see [handle app user sign out](../../../bots/how-to/authentication/bot-sso-code.md#handle-app-user-sign-out). ## Code sample | **Sample name** | **Description** | **.NET** | **Node.js** | **Manifest** | | --- | --- | --- | --- | --- | -| SSO for your Adaptive Cards Universal Actions | This sample code demonstrates how to enable SSO authentication for your Adaptive Cards. | [View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/bot-sso-adaptivecard/csharp) | [View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/bot-sso-adaptivecard/nodejs) | [View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/bot-sso-adaptivecard/csharp/demo-manifest) | +| SSO for your Adaptive Cards Universal Actions | This sample code demonstrates how to enable SSO authentication for your Adaptive Cards. | [View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/bot-sso-adaptivecard/csharp) | [View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/bot-sso-adaptivecard/nodejs) | [View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/bot-sso-adaptivecard/csharp/demo-manifest) | ## See also From 4366f60bf3819cc6cbfb03406e090521eacf5534 Mon Sep 17 00:00:00 2001 From: v-nikitach Date: Tue, 26 May 2026 15:58:43 +0530 Subject: [PATCH 3/7] made changes to 3 files --- .../how-to/authentication/bot-sso-code.md | 20 +++++++++---------- .../messaging-extensions/msgex-sso-code.md | 4 ++++ .../sso-adaptive-cards-universal-action.md | 2 +- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/msteams-platform/bots/how-to/authentication/bot-sso-code.md b/msteams-platform/bots/how-to/authentication/bot-sso-code.md index 7acdeb49e0c..23c3f4d090c 100644 --- a/msteams-platform/bots/how-to/authentication/bot-sso-code.md +++ b/msteams-platform/bots/how-to/authentication/bot-sso-code.md @@ -4,16 +4,16 @@ description: Learn how to add code configuration, handle an access token, receiv ms.topic: how-to ms.localizationpriority: high zone_pivot_groups: enable-sso -ms.date: 05/25/2026 +ms.date: 05/26/2026 --- # Add code to enable SSO in your bot app Before you add code to enable single sign-on (SSO), ensure to configure your app and bot resource in Microsoft Entra admin center. > [!div class="nextstepaction"] -> [Configure bot app in Microsoft Entra ID](bot-sso-register-aad.md) +> [Configure bot app in Azure Bot Service](bot-sso-register-aad.md) -You need to configure your app's code to obtain an access token from Microsoft Entra ID. The access token is issued on behalf of the bot app. +You need to configure your app's code to obtain an access token from Azure Bot Service. The access token is issued on behalf of the bot app. > [!NOTE] > If you've built your Teams app using Microsoft Teams Toolkit, you can enable SSO for your app using the instructions in the Tools and SDKs module. For more information, see [add single sign-on to Teams app](../../../toolkit/add-single-sign-on.md). Teams Toolkit supports SSO for JavaScript, TypeScript, and C# apps in Visual Studio Code. @@ -30,7 +30,7 @@ This section covers: ## Update development environment variables -You've configured client secret and OAuth connection setting for the app in Microsoft Entra ID. You must configure the code with these values. +You've configured client secret and OAuth connection setting for the app in Azure Bot Service. You must configure the code with these values. To update the development environment variables: @@ -38,9 +38,9 @@ To update the development environment variables: 1. Open the environment file (`.env`) for your project. 1. Update the following variables: - - For `CLIENT_ID`, update the bot ID from Microsoft Entra ID. + - For `CLIENT_ID`, update the bot ID from Azure Bot Service. - For `CLIENT_SECRET`, update the client secret. - - For `CONNECTION_NAME`, update the name of the OAuth connection you configured in Microsoft Entra ID. + - For `CONNECTION_NAME`, update the name of the OAuth connection you configured in Azure Bot Service. - For `TENANT_ID`, update the tenant ID. > [!NOTE] @@ -86,7 +86,7 @@ const app = new App({ ```python import os -from teams import App +from microsoft_teams.apps import App app = App(default_connection_name=os.getenv("CONNECTION_NAME", "graph")) ``` @@ -246,7 +246,7 @@ async def handle_sign_in(event: SignInEvent): When using SSO, if the token exchange fails, Teams sends a `signin/failure` invoke activity to your app. The SDK includes a built-in default handler that logs a warning with actionable troubleshooting guidance. You can optionally register your own handler to customize the behavior: -# [C#](#tab/cs-failure) +# [C#](#tab/cs5) ```csharp teams.OnSignInFailure(async (context, cancellationToken) => @@ -257,7 +257,7 @@ teams.OnSignInFailure(async (context, cancellationToken) => }); ``` -# [TypeScript](#tab/ts-failure) +# [TypeScript](#tab/ts5) ```typescript app.on('signin.failure', async ({ activity, send }) => { @@ -267,7 +267,7 @@ app.on('signin.failure', async ({ activity, send }) => { }); ``` -# [Python](#tab/py-failure) +# [Python](#tab/py5) ```python @app.on_signin_failure() diff --git a/msteams-platform/includes/messaging-extensions/msgex-sso-code.md b/msteams-platform/includes/messaging-extensions/msgex-sso-code.md index 1c52f53f8a0..403dc83df33 100644 --- a/msteams-platform/includes/messaging-extensions/msgex-sso-code.md +++ b/msteams-platform/includes/messaging-extensions/msgex-sso-code.md @@ -82,6 +82,7 @@ const app = new App({ # [Python](#tab/py2) ```python +import os from microsoft_teams.apps import App, AppOptions app = App( @@ -358,6 +359,9 @@ app.message('/signout', async ({ send, signout, isSignedIn }) => { # [Python](#tab/py7) ```python +from microsoft_teams.api import MessageActivity +from microsoft_teams.apps import ActivityContext + @app.on_message_pattern("signout") async def handle_signout(ctx: ActivityContext[MessageActivity]): if not ctx.is_signed_in: diff --git a/msteams-platform/task-modules-and-cards/cards/Universal-actions-for-adaptive-cards/sso-adaptive-cards-universal-action.md b/msteams-platform/task-modules-and-cards/cards/Universal-actions-for-adaptive-cards/sso-adaptive-cards-universal-action.md index 2865c7e673f..b3325b63519 100644 --- a/msteams-platform/task-modules-and-cards/cards/Universal-actions-for-adaptive-cards/sso-adaptive-cards-universal-action.md +++ b/msteams-platform/task-modules-and-cards/cards/Universal-actions-for-adaptive-cards/sso-adaptive-cards-universal-action.md @@ -118,7 +118,7 @@ When the app user selects **View and accept**, the existing Microsoft Entra perm 1. The bot returns a non-error response to the Teams client using either a card or message. > [!NOTE] -> To handle the access token in case the app user logs out, see [handle app user sign out](../../../bots/how-to/authentication/bot-sso-code.md#handle-app-user-sign-out). +> To handle the access token in case the app user signs out, see [handle app user sign out](../../../bots/how-to/authentication/bot-sso-code.md#handle-app-user-sign-out). ## Code sample From 3d6f17ebaab11ea25ac56c48b2a94e9a5b0f6a52 Mon Sep 17 00:00:00 2001 From: v-nikitach Date: Tue, 26 May 2026 16:01:59 +0530 Subject: [PATCH 4/7] apply changes Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- msteams-platform/bots/how-to/authentication/bot-sso-code.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/msteams-platform/bots/how-to/authentication/bot-sso-code.md b/msteams-platform/bots/how-to/authentication/bot-sso-code.md index 23c3f4d090c..cd4f7cd1816 100644 --- a/msteams-platform/bots/how-to/authentication/bot-sso-code.md +++ b/msteams-platform/bots/how-to/authentication/bot-sso-code.md @@ -62,8 +62,11 @@ using Microsoft.Teams.Plugins.AspNetCore.Extensions; var builder = WebApplication.CreateBuilder(args); +var connectionName = builder.Configuration["CONNECTION_NAME"] + ?? throw new InvalidOperationException("Missing required configuration value: CONNECTION_NAME"); + var appBuilder = App.Builder() - .AddOAuth("graph"); + .AddOAuth(connectionName); builder.AddTeams(appBuilder); var app = builder.Build(); From 907bcfb54460c94a749d21be13135d0c2866ef5b Mon Sep 17 00:00:00 2001 From: v-nikitach Date: Tue, 26 May 2026 16:04:18 +0530 Subject: [PATCH 5/7] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- msteams-platform/bots/how-to/authentication/bot-sso-code.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/msteams-platform/bots/how-to/authentication/bot-sso-code.md b/msteams-platform/bots/how-to/authentication/bot-sso-code.md index cd4f7cd1816..8708da555b4 100644 --- a/msteams-platform/bots/how-to/authentication/bot-sso-code.md +++ b/msteams-platform/bots/how-to/authentication/bot-sso-code.md @@ -78,9 +78,11 @@ var teams = app.UseTeams(); ```typescript import { App } from '@microsoft/teams.apps'; +const connectionName = process.env.CONNECTION_NAME ?? 'graph'; + const app = new App({ oauth: { - defaultConnectionName: 'graph', + defaultConnectionName: connectionName, }, }); ``` From 55045dcc19140fbdb4928463ee11aa322321e460 Mon Sep 17 00:00:00 2001 From: v-nikitach Date: Tue, 26 May 2026 16:06:34 +0530 Subject: [PATCH 6/7] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../includes/messaging-extensions/msgex-sso-code.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/msteams-platform/includes/messaging-extensions/msgex-sso-code.md b/msteams-platform/includes/messaging-extensions/msgex-sso-code.md index 403dc83df33..acb26ba5443 100644 --- a/msteams-platform/includes/messaging-extensions/msgex-sso-code.md +++ b/msteams-platform/includes/messaging-extensions/msgex-sso-code.md @@ -378,5 +378,5 @@ async def handle_signout(ctx: ActivityContext[MessageActivity]): | **Sample name** | **Description** | **.NET** | **Node.js** | **Python** | |---|---|---|---|---| -| Bot auth quickstart | Demonstrates SSO authentication for Teams bots using the Teams SDK. | [View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/bot-auth-quickstart/dotnet/bot-auth-quickstart) | [View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/bot-auth-quickstart/nodejs/bot-auth-quickstart) | [View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/bot-auth-quickstart/python/bot-auth-quickstart) | +| Bot auth quickstart | Demonstrates SSO authentication for Teams bots using the Teams SDK. | [View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/TeamsSDK/bot-auth-quickstart/dotnet/bot-auth-quickstart) | [View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/TeamsSDK/bot-auth-quickstart/nodejs/bot-auth-quickstart) | [View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/TeamsSDK/bot-auth-quickstart/python/bot-auth-quickstart) | | Bot message extensions | Demonstrates search-based messaging extensions with the Teams SDK. | [View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/bot-message-extensions/dotnet/bot-message-extensions) | [View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/bot-message-extensions/nodejs/bot-message-extensions) | [View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/bot-message-extensions/python/bot-message-extensions) | From d18ea01164aba4ec4874d577985e3fcc3fe52555 Mon Sep 17 00:00:00 2001 From: v-nikitach Date: Tue, 26 May 2026 18:05:05 +0530 Subject: [PATCH 7/7] made updates --- .../bots/how-to/authentication/bot-sso-code.md | 13 +++++-------- .../includes/messaging-extensions/msgex-sso-code.md | 2 +- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/msteams-platform/bots/how-to/authentication/bot-sso-code.md b/msteams-platform/bots/how-to/authentication/bot-sso-code.md index 8708da555b4..ac11072ea06 100644 --- a/msteams-platform/bots/how-to/authentication/bot-sso-code.md +++ b/msteams-platform/bots/how-to/authentication/bot-sso-code.md @@ -11,9 +11,9 @@ ms.date: 05/26/2026 Before you add code to enable single sign-on (SSO), ensure to configure your app and bot resource in Microsoft Entra admin center. > [!div class="nextstepaction"] -> [Configure bot app in Azure Bot Service](bot-sso-register-aad.md) +> [Configure bot app in Microsoft Entra ID](bot-sso-register-aad.md) -You need to configure your app's code to obtain an access token from Azure Bot Service. The access token is issued on behalf of the bot app. +You need to configure your app's code to obtain an access token from Microsoft Entra ID. The access token is issued on behalf of the bot app. > [!NOTE] > If you've built your Teams app using Microsoft Teams Toolkit, you can enable SSO for your app using the instructions in the Tools and SDKs module. For more information, see [add single sign-on to Teams app](../../../toolkit/add-single-sign-on.md). Teams Toolkit supports SSO for JavaScript, TypeScript, and C# apps in Visual Studio Code. @@ -30,7 +30,7 @@ This section covers: ## Update development environment variables -You've configured client secret and OAuth connection setting for the app in Azure Bot Service. You must configure the code with these values. +You've configured client secret and OAuth connection setting for the app in Microsoft Entra ID. You must configure the code with these values. To update the development environment variables: @@ -38,9 +38,9 @@ To update the development environment variables: 1. Open the environment file (`.env`) for your project. 1. Update the following variables: - - For `CLIENT_ID`, update the bot ID from Azure Bot Service. + - For `CLIENT_ID`, update the bot ID from Microsoft Entra ID. - For `CLIENT_SECRET`, update the client secret. - - For `CONNECTION_NAME`, update the name of the OAuth connection you configured in Azure Bot Service. + - For `CONNECTION_NAME`, update the name of the OAuth connection you configured in Microsoft Entra ID. - For `TENANT_ID`, update the tenant ID. > [!NOTE] @@ -284,9 +284,6 @@ async def handle_signin_failure(ctx): --- -> [!TIP] -> The most common failure codes are `installedappnotfound` (bot app not installed for the user) and `resourcematchfailed` (Token Exchange URL doesn't match the Application ID URI). For more information, see [SSO Troubleshooting](/microsoftteams/platform/teams-sdk/teams/user-authentication/troubleshooting-sso). - ## Handle app user sign out Call the `signout` method to remove the user's authentication token from the User Token service cache, effectively signing them out. The Teams SDK replaces the previous pattern of using `DialogContext`, `UserTokenClient`, and `CancelAllDialogsAsync` with a simple method call. diff --git a/msteams-platform/includes/messaging-extensions/msgex-sso-code.md b/msteams-platform/includes/messaging-extensions/msgex-sso-code.md index acb26ba5443..1f1a02d7b40 100644 --- a/msteams-platform/includes/messaging-extensions/msgex-sso-code.md +++ b/msteams-platform/includes/messaging-extensions/msgex-sso-code.md @@ -379,4 +379,4 @@ async def handle_signout(ctx: ActivityContext[MessageActivity]): | **Sample name** | **Description** | **.NET** | **Node.js** | **Python** | |---|---|---|---|---| | Bot auth quickstart | Demonstrates SSO authentication for Teams bots using the Teams SDK. | [View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/TeamsSDK/bot-auth-quickstart/dotnet/bot-auth-quickstart) | [View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/TeamsSDK/bot-auth-quickstart/nodejs/bot-auth-quickstart) | [View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/TeamsSDK/bot-auth-quickstart/python/bot-auth-quickstart) | -| Bot message extensions | Demonstrates search-based messaging extensions with the Teams SDK. | [View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/bot-message-extensions/dotnet/bot-message-extensions) | [View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/bot-message-extensions/nodejs/bot-message-extensions) | [View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/bot-message-extensions/python/bot-message-extensions) | +| Bot message extensions | Demonstrates search-based messaging extensions with the Teams SDK. | [View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/TeamsSDK/bot-message-extensions/dotnet/bot-message-extensions) | [View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/TeamsSDK/bot-message-extensions/nodejs/bot-message-extensions) | [View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/TeamsSDK/bot-message-extensions/python/bot-message-extensions) |