Add single sign-on to Teams app

Microsoft Teams provides single sign-on (SSO) function for an app to obtain signed in Teams user token to access Microsoft Graph and other APIs. Teams Toolkit facilitates the interaction by abstracting few of the Microsoft Azure Active Directory (Azure AD) flows and integrations behind simple APIs. This enables you to add SSO features easily to your Teams app.

Add SSO to Teams app for Microsoft Visual Studio Code

For apps that interact with the user in a chat, Team, or channel, SSO manifests as an Adaptive Card, which the user can interact with to invoke the Azure AD consent flow.

Enable SSO support

Teams Toolkit helps you to add SSO to the following Teams capabilities in Visual Studio Code:

  • Tab
  • Bot
  • Notification bot: restify server
  • Command bot
  • Workflow bot
  • Message extension

Add SSO using Visual Studio Code

You can perform the following steps to add SSO using Teams Toolkit in Visual Studio Code:

  1. Open Visual Studio Code.

  2. Select Teams Toolkit from the Visual Studio Code activity bar.

  3. Select View How-to Guides in the DEVELOPMENT section.

    Screenshot shows the selection of View How-to guides option.

  4. From the dropdown list, select Develop Single Sign-On Experience in Teams. You'll be redirected to the respective How-to guide.

    Screenshot shows the Single Sign-on feature highlighted in red in the Visual Studio Code.

    Development How-to Guide
    Develop Single Sign-on Experience in Teams How to Develop Single Sign-on Experience

Note

When SSO is enabled, Teams Toolkit by default provisions a single-tenant Azure AD app, which means only user and guest accounts in the same directory as your M365 account can sign in to your Teams app. For more information on supporting multi-tenant to update your TeamsFx project, see Multi-tenancy support for Azure AD app.

See also

Important

We've introduced the Teams Toolkit v5 extension within Visual Studio Code. This version comes to you with many new app development features. We recommend that you use Teams Toolkit v5 for building your Teams app.

Teams Toolkit v4 extension will soon be deprecated.

Microsoft Teams provides single sign-on (SSO) function for an app to obtain signed in Teams user token to access Microsoft Graph and other APIs. Teams Toolkit facilitates the interaction by abstracting some of the Microsoft Azure Active Directory (Azure AD) flows and integrations behind some simple APIs. This enables you to add SSO features easily to your Teams app.

Add SSO to Teams app for Microsoft Visual Studio Code

For apps that interact with the user in a chat, Team, or channel, SSO manifests as an Adaptive Card, which the user can interact with to invoke the Azure AD consent flow.

Enable SSO support

Teams Toolkit helps you to add SSO to the following Teams capabilities in Visual Studio Code:

  • Tab
  • Bot
  • Notification bot: restify server
  • Command bot
  • Workflow bot
  • Message extension

Add SSO using Visual Studio Code

You can perform the following steps to add SSO using Teams Toolkit in Visual Studio Code:

  1. Open Visual Studio Code.

  2. Select Teams Toolkit from the Visual Studio Code activity bar.

  3. Select Add features under DEVELOPMENT.

    Screenshot shows the Add features option under the Development option in the Visual Studio Code.

    • You can select View > Command Palette... to view Add features window.
  4. Select Single Sign-On.

    Screenshot shows the Single Sign-on feature highlighted in red in the Visual Studio Code.

Add SSO using TeamsFx CLI

You can run teamsfx add sso command in your project root directory.

Note

The feature enables SSO for all existing applicable capabilities. Follow the same steps to enable SSO if you add capability later to the project.

Customize your project using Teams Toolkit

The following table lists the changes Teams Toolkit makes to your project:

Type File Purpose
Create aad.template.json under template\appPackage Azure AD application manifest represents your Azure AD app. template\appPackage helps to register an Azure AD app during local debug or provision stage.
Modify manifest.template.json under template\appPackage A webApplicationInfo object is added into your app manifest (previously called Teams app manifest) template. Teams requires this field to enable SSO. The change is in effect when you trigger local debugging or provisioning.
Create auth\tab Reference code, auth redirect pages, and README.md files are generated in this path for a tab project.
Create auth\bot Reference code, auth redirect pages, and README.md files are generated in this path for a bot project.

Note

By adding SSO, Teams Toolkit doesn't change anything in the cloud until you trigger local debug. Update your code to ensure that SSO is working in the project.

Update your application to use SSO

To enable SSO in your application, follow these steps:

Note

These changes are based on the templates you scaffold.




Tab project
  1. Copy auth-start.html and auth-end.htm in auth\public folder to tabs\public\. Teams Toolkit registers these two endpoints in Azure AD for Azure AD's redirect flow.

  2. Copy sso folder under auth\tab to tabs\src\sso\.

    • InitTeamsFx: The file implements a function that initializes TeamsFx SDK and opens GetUserProfile component after SDK is initialized.

    • GetUserProfile: The file implements a function that calls Microsoft Graph API to get user info.

  3. Execute npm install @microsoft/teamsfx-react under tabs\.

  4. Add the following lines to tabs\src\components\sample\Welcome.tsx to import InitTeamsFx:

    
    import { InitTeamsFx } from "../../sso/InitTeamsFx";
    
    
  5. Replace the following line:

    <AddSSO /> with <InitTeamsFx /> to replace the AddSso component with InitTeamsFx component.

Bot project

Set up the Azure AD redirects

  1. Move the auth\bot\public folder to bot\src. This folder contains HTML pages that the bot app hosts. When SSO flow is initiated with Azure AD, it redirects you to the HTML pages.

  2. Modify your bot\src\index to add the appropriate restify routes to HTML pages.

    const path = require("path");
    
    server.get(
      "/auth-*.html",
      restify.plugins.serveStatic({
        directory: path.join(__dirname, "public"),
      })
    );
    

Update your app

SSO command handler ProfileSsoCommandHandler uses an Azure AD token to call Microsoft Graph. This token is obtained by using the signed in Teams user token. The flow is brought together in a dialog that displays a consent dialog if necessary.

  1. Move profileSsoCommandHandler file under auth\bot\sso folder to bot\src. ProfileSsoCommandHandler class is an SSO command handler to get user info with SSO token, follow this method and create your own SSO command handler.

  2. Open package.json file and ensure that TeamsFx SDK version >= 1.2.0.

  3. Execute the npm install isomorphic-fetch --save command in the bot folder.

  4. For ts script, execute the npm install copyfiles --save-dev command in the bot folder and replace following lines in package.json:

    "build": "tsc --build && shx cp -r ./src/adaptiveCards ./lib/src",
    

    with

    "build": "tsc --build && shx cp -r ./src/adaptiveCards ./lib/src && copyfiles src/public/*.html lib/",
    

    This copies the HTML pages used for auth redirect when building the bot project.

  5. To make SSO consent flow work, replace the following code in bot\src\index file:

    server.post("/api/messages", async (req, res) => {
      await commandBot.requestHandler(req, res);
    });
    

    with

    server.post("/api/messages", async (req, res) => {
      await commandBot.requestHandler(req, res).catch((err) => {
        // Error message including "412" means it is waiting for user's consent, which is a normal process of SSO, shouldn't throw this error.
        if (!err.message.includes("412")) {
          throw err;
        }
      });
    });
    
  6. Replace the following options for ConversationBot instance in bot\src\internal\initialize to add the SSO config and SSO command handler:

    export const commandBot = new ConversationBot({
        ...
        command: {
            enabled: true,
            commands: [new HelloWorldCommandHandler()],
        },
    });
    

    with

    import { ProfileSsoCommandHandler } from "../profileSsoCommandHandler";
    
    export const commandBot = new ConversationBot({
        ...
        // To learn more about ssoConfig, please refer teamsfx sdk document: https://docs.microsoft.com/microsoftteams/platform/toolkit/teamsfx-sdk
        ssoConfig: {
            aad :{
                scopes:["User.Read"],
            },
        },
        command: {
            enabled: true,
            commands: [new HelloWorldCommandHandler() ],
            ssoCommands: [new ProfileSsoCommandHandler()],
        },
    });
    
  7. Register your command in the Teams app manifest. Open templates\appPackage\manifest.template.json, and add the following lines under commands in commandLists of your bot:

    {
      "title": "profile",
      "description": "Show user profile using Single Sign On feature"
    }
    

Add a new SSO command to the bot (Optional)

After successfully adding SSO in your project, you can add a new SSO command:

  1. Create a new file such as photoSsoCommandHandler.ts or photoSsoCommandHandler.js in bot\src\ and add your own SSO command handler to call Graph API:

    // for TypeScript:
    import { Activity, TurnContext, ActivityTypes } from "botbuilder";
    import "isomorphic-fetch";
    import {
        CommandMessage,
        TriggerPatterns,
        TeamsFx,
        createMicrosoftGraphClient,
        TeamsFxBotSsoCommandHandler,
        TeamsBotSsoPromptTokenResponse,
    } from "@microsoft/teamsfx";
    
    export class PhotoSsoCommandHandler implements TeamsFxBotSsoCommandHandler {
        triggerPatterns: TriggerPatterns = "photo";
    
        async handleCommandReceived(
            context: TurnContext,
            message: CommandMessage,
            tokenResponse: TeamsBotSsoPromptTokenResponse,
        ): Promise<string | Partial<Activity> | void> {
            await context.sendActivity("Retrieving user information from Microsoft Graph ...");
    
            const teamsfx = new TeamsFx().setSsoToken(tokenResponse.ssoToken);
    
            const graphClient = createMicrosoftGraphClient(teamsfx, ["User.Read"]);
    
            let photoUrl = "";
            try {
                const photo = await graphClient.api("/me/photo/$value").get();
                const arrayBuffer = await photo.arrayBuffer();
                const buffer=Buffer.from(arrayBuffer, 'binary');
                photoUrl = "data:image/png;base64," + buffer.toString("base64");
            } catch {
                // Could not fetch photo from user's profile, return empty string as placeholder.
            }
            if (photoUrl) {
                const photoMessage: Partial<Activity> = {
                    type: ActivityTypes.Message,
                    text: 'This is your photo:',
                    attachments: [
                        {
                            name: 'photo.png',
                            contentType: 'image/png',
                            contentUrl: photoUrl
                        }
                    ]
                };
                return photoMessage;
            } else {
                return "Could not retrieve your photo from Microsoft Graph. Please make sure you have uploaded your photo.";
            }
        }
    }
    
    // for JavaScript:
    const { ActivityTypes } = require("botbuilder");
    require("isomorphic-fetch");
    const {
      createMicrosoftGraphClient,
      TeamsFx,
    } = require("@microsoft/teamsfx");
    
    class PhotoSsoCommandHandler {
      triggerPatterns = "photo";
    
      async handleCommandReceived(context, message, tokenResponse) {
        await context.sendActivity(
          "Retrieving user information from Microsoft Graph ..."
        );
    
        const teamsfx = new TeamsFx().setSsoToken(tokenResponse.ssoToken);
    
        const graphClient = createMicrosoftGraphClient(teamsfx, ["User.Read"]);
    
        let photoUrl = "";
        try {
          const photo = await graphClient.api("/me/photo/$value").get();
          const arrayBuffer = await photo.arrayBuffer();
          const buffer = Buffer.from(arrayBuffer, "binary");
          photoUrl = "data:image/png;base64," + buffer.toString("base64");
        } catch {
          // Could not fetch photo from user's profile, return empty string as placeholder.
        }
        if (photoUrl) {
          const photoMessage = {
            type: ActivityTypes.Message,
            text: "This is your photo:",
            attachments: [
              {
                name: "photo.png",
                contentType: "image/png",
                contentUrl: photoUrl,
              },
            ],
          };
          return photoMessage;
        } else {
          return "Could not retrieve your photo from Microsoft Graph. Please make sure you have uploaded your photo.";
        }
      }
    }
    
    module.exports = {
      PhotoSsoCommandHandler,
    };
    
  2. Add PhotoSsoCommandHandler instance to ssoCommands array in bot\src\internal\initialize.ts:

    // for TypeScript:
    import { PhotoSsoCommandHandler } from "../photoSsoCommandHandler";
    
    export const commandBot = new ConversationBot({
        ...
        command: {
            ...
            ssoCommands: [new ProfileSsoCommandHandler(), new PhotoSsoCommandHandler()],
        },
    });
    
    // for JavaScript:
    ...
    const { PhotoSsoCommandHandler } = require("../photoSsoCommandHandler");
    
    const commandBot = new ConversationBot({
        ...
        command: {
            ...
            ssoCommands: [new ProfileSsoCommandHandler(), new PhotoSsoCommandHandler()]
        },
    });
    ...
    
    
  3. Register your command in app manifest. Open templates\appPackage\manifest.template.json, and add following lines under commands in commandLists of your bot:

    
    {
        "title": "photo",
        "description": "Show user photo using Single Sign On feature"
    }
    
    
Message extension project

The sample business logic provides a handler TeamsBot extends TeamsActivityHandler and override handleTeamsMessagingExtensionQuery.

You can update the query logic in the handleMessageExtensionQueryWithToken with token, which is obtained by using the signed in Teams user token.

To make this work in your app:

  1. Move the auth\bot\public folder to bot. This folder contains HTML pages that the bot app hosts. When SSO flows are initiated with Azure AD, Azure AD redirects you to these pages.

  2. Modify your bot\index to add the appropriate restify routes to these pages.

    const path = require("path");
    
    server.get(
        "/auth-*.html",
        restify.plugins.serveStatic({
            directory: path.join(__dirname, "public"),
        })
    );
    
  3. Override handleTeamsMessagingExtensionQuery interface under bot\teamsBot. You can follow the sample code in the handleMessageExtensionQueryWithToken to do your own query logic.

  4. Open bot\package.json, ensure that @microsoft/teamsfx version >= 1.2.0

  5. Install isomorphic-fetch npm packages in your bot project.

  6. For ts, install copyfiles npm packages in your bot project, add or update the build script in bot\package.json as follows:

    "build": "tsc --build && copyfiles ./public/*.html lib/",
    

    As you do it, the HTML pages used for auth redirect are copied when you build this bot project.

  7. Update templates\appPackage\aad.template.json your scopes, which used in handleMessageExtensionQueryWithToken.

    "requiredResourceAccess": [
        {
            "resourceAppId": "Microsoft Graph",
            "resourceAccess": [
                {
                    "id": "User.Read",
                    "type": "Scope"
                }
            ]
        }
    ]
    

Debug your app

Press F5 to debug your application. Teams Toolkit uses the Azure AD app manifest file to register an Azure AD app for SSO. For Teams Toolkit local debug functionalities, see how to debug your app locally.

Customize Azure AD app registration

The Azure AD app manifest allows you to customize various aspects of app registration. You can update the app manifest as needed. If you need to include more API permissions to access your desired APIs, see API permissions to access your desired APIs. To view your Azure AD app in Azure portal, see view Azure AD app in Azure portal.

SSO authentication concepts

The following concepts help you with SSO authentication:

Working of SSO in Teams

SSO authentication in Azure AD silently refreshes the authentication token to minimize the number of times users need to enter their sign in credentials. If users agree to use your app, they don't have to provide consent again on another device, as they're signed in automatically.

Teams tabs and bots have similar flow for SSO support. For more information, see:

  1. SSO authentication in Tabs
  2. SSO authentication in Bots

Simplified SSO with TeamsFx

TeamsFx helps to reduce the developer tasks by using SSO and accessing cloud resources down to single-line statements with zero configuration.

With TeamsFx SDK, you can write user authentication code in a simplified way using following credentials:

  1. User identity in browser environment: TeamsUserCredential represents Teams current user's identity.
  2. User identity in Node.js environment: OnBehalfOfUserCredential uses On-Behalf-Of flow and SSO token.
  3. Application Identity in Node.js environment: AppCredential represents the application identity.

For more information about TeamsFx SDK, see:

See also