Skip to content
This repository has been archived by the owner on Jun 30, 2022. It is now read-only.

Enable MS teams Channel #3792

Closed
SreekanthOAuth opened this issue Apr 29, 2021 · 14 comments
Closed

Enable MS teams Channel #3792

SreekanthOAuth opened this issue Apr 29, 2021 · 14 comments
Assignees
Labels
Bot Services Required for internal Azure reporting. Do not delete. Do not change color. ExemptFromDailyDRIReport Use this label to exclude the issue from the DRI report. Needs Triage Needs to be triaged for assignment Team: Kobuk This issue is assigned to the Kobuk team. Type: Bug Something isn't working

Comments

@SreekanthOAuth
Copy link

SreekanthOAuth commented Apr 29, 2021

What project is affected?

Virtual Assistant with botbuilder version 4.11.0

What language is this in?

Typescript

What happens?

We are enabling the MS teams channel for our bot (already enabled Directline channel which is up & running).

We have tried the below samples but those are not helpful.

https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/bot-conversation-sso-quickstart
https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/tab-sso/nodejs

We are configured OAuth setting as AAD with scopes are openid profile User.Read (also tried adding offline_access) at BOT service and when we try testing the connection we are able to get the token.

There is a membersAdded method in defaultActivityHandler.ts file, which is getting executed when clicking on the ADD button in App in teams but not seeing any token (authToken & idtoken) in turncontext.

We try logging from OAuthPrompt (from teams channel) but here also not getting the tokens.

What are the steps to reproduce this issue?

Create the virtual assistant by the following the URL,

https://microsoft.github.io/botframework-solutions/virtual-assistant/tutorials/create-assistant/typescript/3-create-project/

manifest.json file is

{ "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.9/MicrosoftTeams.schema.json", "manifestVersion": "1.9", "version": "1.0.0", "showLoadingIndicator": true, "isFullScreen": true, "id": "aa186c9e-f7a1-4c43-b68e-22988a3f871b", "packageName": "com.microsoft.teams.eva-it-dev", "developer": { "name": "Ecolab", "websiteUrl": "https://www.xxxx.com", "privacyUrl": "https://www.xxxx.com/epp", "termsOfUseUrl": "https://www.xxxx.com/terms-of-use" }, "icons": { "color": "color.png", "outline": "outline.png" }, "name": { "short": "DEV-TEST", "full": "TEST" }, "description": { "short": "TEST", "full": "Teams" }, "accentColor": "#FFFFFF", "staticTabs": [ { "entityId": "conversations", "scopes": [ "personal" ] }, { "entityId": "about", "scopes": [ "personal" ] }, { "entityId": "com.contoso.teamsauthsample.static", "name": "Auth Tab", "contentUrl": "https://0e9efa6a1a73.ngrok.io/api/healthcheck", "scopes": [ "personal" ] } ], "bots": [ { "botId": "XXXX-3cac-4c8d-bd6b-XXXX", "scopes": [ "personal" ], "supportsFiles": false, "isNotificationOnly": false } ], "permissions": [ "identity", "messageTeamMembers" ], "validDomains": [ "en-in.xxxx.com", "www.xxxxx.com", "dev.xx.xxx.com", "token.botframework.com", "*.ngrok.io" ], "webApplicationInfo": { "id": "XXXX-50f6-4d38-9ec0-XXXX", "resource": "api://0e9efa6a1a73.ngrok.io/XXX-50f6-4d38-9ec0-XXXXX" } }

What were you expecting to happen?

We are looking for SSO in teams channel.

Please suggest us on getting the tokens for the MS teams channel. And also please suggest the recommended manifest.json file.

Thanks,
Sreekanth (Ecolab)

@SreekanthOAuth SreekanthOAuth added Needs Triage Needs to be triaged for assignment Type: Bug Something isn't working labels Apr 29, 2021
@Kmeredith-hcg
Copy link

This is functional but it takes a good bit of work. My company has it deployed in Teams and functional with the oAuth directions provided in the documents. This was taken from the Teams-Authbot project and modified to work with the template and what we found that is needed in the maindialog.cs is the following:

Add the following steps into your waterfall, PromptStepAsync and LoginStepAsync, these need to be first and second
Next, After the waterfallstep initialization Add the following:

AddDialog(new OAuthPrompt(nameof(OAuthPrompt), new OAuthPromptSettings
{
ConnectionName = "Your_oAuth_AAD_Connection_Here",
Text = "Your_Text_Here",
Title = "Sign in",
Timeout = 9900000,
}));

Your Appsettings.json should have the following:

"tokenExchangeConfig": {
"connectionName": "Your_oAuth_AAD_Name_Here",
"provider": "Azure Active Directory v2"
},

Yes I know the location does not seem correct per the docs but it works.

For the two waterfallsteps added earlier, add the following functions:

private async Task PromptStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
return await stepContext.BeginDialogAsync(nameof(OAuthPrompt), null, cancellationToken);
}

    private async Task<DialogTurnResult> LoginStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
    {
        var userProfile = await _userProfileState.GetAsync(stepContext.Context, () => new UserProfileState(), cancellationToken);
        // 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.
        var tokenResponse = (TokenResponse)stepContext.Result;
        //Function to skip this after it has been done once
        if (tokenResponse?.Token == null)
        {
            await stepContext.Context.SendActivityAsync(MessageFactory.Text("Login was not successful please try again."), cancellationToken);
            return await stepContext.EndDialogAsync(cancellationToken: cancellationToken);
        }
        else if (!string.IsNullOrEmpty(userProfile.Name))
        {
            return await stepContext.NextAsync(cancellationToken: cancellationToken);
        }
        else
        {
            // Pull in the data from the Microsoft Graph.
            var client = new SimpleGraphClient(tokenResponse.Token);
            var me = await client.GetMeAsync();
            var title = !string.IsNullOrEmpty(me.JobTitle) ?
                        me.JobTitle : "Unknown";

            await stepContext.Context.SendActivityAsync($"You're logged in as {me.DisplayName} ({me.UserPrincipalName}); your job title is: {title}");
            userProfile.Name = me.DisplayName;
            userProfile.UserPrincipalName = me.UserPrincipalName;

            await _userProfileState.SetAsync(stepContext.Context, userProfile, cancellationToken);

            await stepContext.Context.SendActivityAsync(_templateManager.GenerateActivityForLocale("HaveNameMessage", userProfile), cancellationToken);

            return await stepContext.NextAsync(cancellationToken: cancellationToken);
        }

You can modify the LoginStepAsync as needed for what it displays but this worked for our company. Now you need to add the Microsoft Graph class, SimpleGraphClient.cs, from the TeamsAuth bot located in the GitHub samples https://github.com/microsoft/BotBuilder-Samples/tree/main/samples/csharp_dotnetcore

This should get your bot secured to your tenant and being able to prompt for authorization in Teams.

Hope this helps.

@GeoffCoxMSFT GeoffCoxMSFT added customer-reported Issue is created by anyone that is not a collaborator in the repository. Bot Services Required for internal Azure reporting. Do not delete. Do not change color. Team: Kobuk This issue is assigned to the Kobuk team. labels Apr 30, 2021
@Batta32
Copy link
Collaborator

Batta32 commented Apr 30, 2021

Thanks @SreekanthOAuth for reporting this issue. The next week we will start working on this issue but you can check the comment of @Kmeredith-hcg and check if it works for you.

We will let you know as soon as we have any update 😊.

@SreekanthOAuth
Copy link
Author

@Kmeredith-hcg - Thank you so much for the information. We have also tried almost the same in typescript but it's not giving the tokens.

@tsuwandy tsuwandy added the customer-replied-to Indicates that the team has replied to the issue reported by the customer. Do not delete. label May 3, 2021
@VictorGrycuk
Copy link
Contributor

Hi @SreekanthOAuth, we are analysing the issue, and we came up with some questions:

  1. Which version of bot-solutions library are you using?
  2. Are you able to get the token in another channel other than Teams? You mention that AAD was working, but after enabling Teams channel didn't work any more.
  3. Which documentation did you follow to enable SSO?
  4. Are you using a Virtual Assistant alone, or does it have a Skill connected to accomplish the SSO?

We will get back to you as soon as we have an update 🙂.

@SreekanthOAuth
Copy link
Author

Hi @VictorGrycuk, thanks for the response.

Please find the answers to your questions,

  1. Which version of bot-solutions library are you using? [Ans]: "bot-solutions": "^1.0.0",
  2. Are you able to get the token in another channel other than Teams? You mention that AAD was working, but after enabling Teams channel didn't work any more. [Ans]: Yes, we are getting the token in the emulator and webchat (directline). It's only not working for the Teams channel.
  3. Which documentation did you follow to enable SSO? [Ans] : https://github.com/MicrosoftDocs/msteams-docs/blob/master/msteams-platform/tabs/how-to/authentication/auth-aad-sso.md
  4. Are you using a Virtual Assistant alone, or does it have a Skill connected to accomplish the SSO? [Ans]: Yes, we have skill also. But skill will not be part of SSO.

Please let us know if you need further details on this.

@SreekanthOAuth
Copy link
Author

@VictorGrycuk - We are able to get the token now.

the issue is - Since it's a MS teams channel, the history would be present. So, we had canceled all the dialogues and now we started getting the token from OAuthCardPrompt.

And now we are trying for SSO, following the below URL to enable it but not getting any tokens. This time we are using AAD v2.

https://docs.microsoft.com/en-us/microsoftteams/platform/bots/how-to/authentication/auth-aad-sso-bots

Based on this section, we need to pass TokenExchangeResource property. But we don't find this in the Typescript sample.

Could you please provide assistance on this.

@VictorGrycuk
Copy link
Contributor

Hi @SreekanthOAuth, sorry for the delay.

In regard to your question about TokenExchangeResource no existing in TypeScript, that is because that class was implemented in version 1.0 of Virtual Assistant, which as yet has not been released.

We followed the guide to set up the JavaScript Teams Bot with SSO, and we were successful on enabling SSO in Teams and to see the token inside the stepContext in the login step.

We also recommend taking a look at the SSO with Simple Skill Consumer and Skill experimental sample. While it is written for C#, the implementation of the token exchange is quite similar as the one used in the previous sample.

The SSO with Simple Skill Consumer and Skill sample implementation is quite similar to the suggestions made by @Kmeredith-hcg in a previous comment, so it would be worth to review that as well.

Finally, we noticed that you have added your ngrok address in the webApplicationInfo in the manifest (api://0e9efa6a1a73.ngrok.io/XXX-50f6-4d38-9ec0-XXXXX ) is this correct? In our tests we used api://botid-<OUR APP ID> as API endpoint as detailed in this guide.

Documentation and samples we have used:

Let us know if this information has been useful to you.

The token obtained after accepting the SSO prompt in Teams
image

image

@SreekanthOAuth
Copy link
Author

@VictorGrycuk , Thanks for the detailed information.

We have followed the same but not getting the token.
And we are not even getting the OAuthPrompt displayed to the User. But I can see the card is getting generated in logs,

{ contentType: 'application/vnd.microsoft.card.oauth',
 content:
  { buttons: [ [Object] ],
    connectionName: 'EvaAADv2',
    tokenExchangeResource:
     { id: 'XXX-22b3-4d2c-825a-XXXX',
       uri: 'api://botid-XXXX-50f6-4d38-9ec0-XXXX',
       providerId: 'XXX-58e3-4a48-bdfd-XXXXX' },
    text:
     'You are accessing a secure private channel. We want you to identify yourself first. Please sign-in with your [Company] credentials' } }

Not sure what & where we are missing.
We are using the below file to create the BOT object.

https://github.com/microsoft/botframework-solutions/blob/master/templates/typescript/samples/sample-assistant/src/bots/defaultActivityHandler.ts

And this is the SignIn dialog. This would be triggered when there is no user profile in the user state accessor from Maindialog.

import {
    StatePropertyAccessor, BotFrameworkSkill} from 'botbuilder';
import {
    ComponentDialog,
    DialogTurnResult,
    OAuthPrompt,
    WaterfallDialog,
    WaterfallStepContext,
} from 'botbuilder-dialogs';
import { BotServices } from '../services/botServices';
import i18next from 'i18next';
import * as EvaConfig from '../config/eva.json' ;
import { IFlagState } from '../models/flagState';
import { IUserState } from '../models/userState';
import { GenerateAccessToken } from '../utils/generateAccessToken';
import { WelcomeDialog } from './welcomeDialog';
enum DialogIds {
    signInDialogName = 'SignDialogName',
    loginPrompt = 'loginPrompt'
}

export class SignIn extends ComponentDialog {

    private readonly loginPrompt: string = 'loginPrompt';
    private readonly connectionSettingName: string = EvaConfig.CONNECTION_SETTING_V2_NAME;
    // private readonly connectionSettingName: string = EvaConfig.CONNECTION_SETTING_V2_NAME;

    // private static readonly responder: OnboardingResponses = new OnboardingResponses();
    private readonly accessor: StatePropertyAccessor<IUserState>;
    private readonly flagStateAccessor: StatePropertyAccessor<IFlagState>;
    private readonly skillContextAccessor: StatePropertyAccessor<BotFrameworkSkill>;
    private readonly showWelcomeOnWebChatAccessor: StatePropertyAccessor<boolean>;
    // Constructor
    constructor(botServices: BotServices, accessor: StatePropertyAccessor<IUserState>,
        flagStateAccessor: StatePropertyAccessor<IFlagState>,
        skillContextAccessor: StatePropertyAccessor<BotFrameworkSkill>,
        showWelcomeOnWebChatAccessor: StatePropertyAccessor<boolean>
    ) {
        super(SignIn.name);
        this.skillContextAccessor = skillContextAccessor;
        this.accessor = accessor;
        this.flagStateAccessor = flagStateAccessor;
        this.showWelcomeOnWebChatAccessor = showWelcomeOnWebChatAccessor;
        this.initialDialogId = DialogIds.signInDialogName;
        const signInStart: ((sc: WaterfallStepContext<IUserState>) => Promise<DialogTurnResult>)[] = [
            this.promptLoginStep.bind(this),
            this.captureToken.bind(this)
        ];

        this.addDialog(new OAuthPrompt(this.loginPrompt, {
            connectionName: this.connectionSettingName,
            title: 'Sign In',
            // tslint:disable-next-line:max-line-length
            text: 'You are accessing a secure private channel. We want you to identify yourself first. Please sign-in with your [Company] credentials',
            timeout: 900000
        }));

        this.addDialog(new WaterfallDialog(this.initialDialogId, signInStart));
    }

    public async promptLoginStep(step: WaterfallStepContext<IUserState>): Promise<DialogTurnResult> {

        console.log(`SignIn promptLoginStep`);
        return step.beginDialog(this.loginPrompt);
    }

    // tslint:disable-next-line: no-any
    public async captureToken(step: WaterfallStepContext<IUserState>): Promise<any> {
        console.log(`step.result ----> ${JSON.stringify(step.result)}`);
        if (step.result) {
            const generateAccessToken: GenerateAccessToken = new GenerateAccessToken();
            // tslint:disable-next-line
            const msGraphtoken: any = await generateAccessToken.getAccesstokenForMSGraph(step.result.token);
            // tslint:disable-next-line: no-unsafe-any
            await generateAccessToken.getUserProfile(msGraphtoken,
                step,
                this.accessor,
                this.flagStateAccessor,
                // tslint:disable-next-line: no-unsafe-any
                step.result.token,
                'manual');

            await this.showWelcomeOnWebChatAccessor.set(step.context, true);
            return step.beginDialog(WelcomeDialog.name);

        } else {
            await step.context.sendActivity(i18next.t('signIn.error'));
            await this.promptLoginStep(step);
        }

    }

}

Could you please check these and let us know what we are missing.

And also, please answer below one

  • Does OAuthPrompt card supports MFA?

@VictorGrycuk
Copy link
Contributor

Hi @SreekanthOAuth, as agreed we are adding the summary of our call here.

We identified 2 problems in the environment:

  1. Teams Bot manifest: the webApplicationInfo property had configured the authentication application ID instead of the bot application ID
    1. Solution: update the webApplicationInfo property to use the sample application ID as the bot's application ID
  2. Azure Configuration: we compared their configuration with ours, and decided to replicate our configuration on their side
    1. Solution:
      1. Make a new authentication application with multi tenant
      2. Deactivate the follow mobile and desktop flow option of the new application
      3. The Bot Channel Registration resource was updated with a new OAuth settings using the newly created application
      4. A new OAuth connection was created using the new authentication application
      5. The new OAuth connection's Tenant ID was configured as "common" since this is required for multi tenants.

Our environment configuration and the modification implemented were based on the step-by-step guide found in the Bot Conversation SSO Quickstart sample.

If you have further questions or comments, please let us know 🙂.

@VictorGrycuk
Copy link
Contributor

Hi @SreekanthOAuth.
We have been researching about how to retrieve the ID token, and we found that these resources might be helpful to you:

I hope you find these resources useful. Meanwhile, we will keep researching to give you an answer 🙂.

@SreekanthOAuth
Copy link
Author

@VictorGrycuk - We have gone through the above articles and most of them are related to the client-side.

We have tried using the below API and able to get the token by using the AAD V1 but not with AAD V2.

curl --location --request POST 'https://login.microsoftonline.com/[tenet]/oauth2/v2.0/token'
--header 'Content-Type: application/x-www-form-urlencoded'
--data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer'
--data-urlencode 'client_id=XXXXX'
--data-urlencode 'client_secret=XXXXXXX'
--data-urlencode 'requested_token_use=on_behalf_of'
--data-urlencode 'scope=openid'
--data-urlencode 'assertion=[access_token]'

With AAD V2 we are getting invalid grant type.

Please let us know if you get any clues based on this.

@Batta32
Copy link
Collaborator

Batta32 commented May 19, 2021

Hi @SreekanthOAuth. Currently, we couldn't obtain the id_token using the bot, also we researched in botbuilder-dotnet and botbuilder-js but we couldn't find any reference of id_token.

Let's differenciate the concepts of access_token and id_token:

  • access_token: used in token-based authentication to gain access to resources by using them as bearer tokens
  • id_token: carries identity information encoded in the token itself, which must be a JWT

If you are trying to retrieve the information of the logged user using the id_token, we found in the shared sample the getMe() method which retrieves their information using the /me endpoint without handling the id_token and it can be implemented in the TypeScript Virtual Assistant.

If that's not the purpose, can you explain the reason to handle the id_token?

Last but no least, we found a way to get id_token and the access_token through the browser by doing a GET request.

We collected the following links for you:

The user's information retrieved by the getMe() method
image

id_token in browser
image

@Batta32
Copy link
Collaborator

Batta32 commented May 28, 2021

Hi @SreekanthOAuth, as discussed by email. We are closing this issue as you were able to solve the problem but feel free to raise a new issue if you have another problem 😊.

We will close the issue as soon as you complete the testing on your Development environments.

@Batta32 Batta32 closed this as completed May 28, 2021
@Batta32 Batta32 reopened this May 28, 2021
@chrimc62 chrimc62 added ExemptFromDailyDRIReport Use this label to exclude the issue from the DRI report. and removed customer-replied-to Indicates that the team has replied to the issue reported by the customer. Do not delete. customer-reported Issue is created by anyone that is not a collaborator in the repository. labels Jun 8, 2021
@Batta32
Copy link
Collaborator

Batta32 commented Jun 8, 2021

Hi @SreekanthOAuth, as discussed by email, we can close this issue 😊.

@Batta32 Batta32 closed this as completed Jun 8, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Bot Services Required for internal Azure reporting. Do not delete. Do not change color. ExemptFromDailyDRIReport Use this label to exclude the issue from the DRI report. Needs Triage Needs to be triaged for assignment Team: Kobuk This issue is assigned to the Kobuk team. Type: Bug Something isn't working
Projects
None yet
Development

No branches or pull requests

7 participants