Virtual Assistant with botbuilder version 4.11.0
Typescript
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.
Create the virtual assistant by the following the URL,
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"
}
}
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)
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
{
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.
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 馃槉.
@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.
Hi @SreekanthOAuth, we are analysing the issue, and we came up with some questions:
We will get back to you as soon as we have an update 馃檪.
Hi @VictorGrycuk, thanks for the response.
Please find the answers to your questions,
Please let us know if you need further details on this.
@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.
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.
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


@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
Hi @SreekanthOAuth, as agreed we are adding the summary of our call here.
We identified 2 problems in the environment:
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 馃檪.
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 馃檪.
@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.
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:
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_

_id_token in browser_

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.
Hi @SreekanthOAuth, as discussed by email, we can close this issue 馃槉.