Reputation: 25
I am trying to create a chat bot using the microsoft botframework and the SDK in typescript. I am trying to have the bot authenticate users and then interact with Azure DevOps on their behalf. However, while I am able to test authentication successfully in Azure portal, when I try to authenticate the user in Teams, the login "succeeds", but the bot returns a 401 error trying to hit the Azure DevOps API's.
I am new to a lot of the microsoft stack, and was following the guide here: https://learn.microsoft.com/en-us/azure/bot-service/bot-builder-authentication?view=azure-bot-service-4.0&tabs=javascript%2Cbot-oauth.
First, I have created my Bot Channels Registration. It has an App Registration associated with it (that was manually created). The appId and secret are set up as environment variables in the App Service on Azure Portal.
Second, I have created another App Registration for my Azure Devops authorization. This app registration has a redirect URI of "https://token.botframework.com/.auth/web/redirect", and has a secret created for it. This app registration also is setup to have the API permissions "user_impersonation" for Azure Devops.
Third, I have gone into my Bot Channels Registration, clicked on the "Settings" blade, and configured an OAuth Connection Setting. This setting has the name "azureDevopsOauth", is setup for an Azure Active Directory V2 service provider, and includes the client id, secret and tenant ID of the app registration I made in the second step. It also has the scope set to "openid".
When I click "Test Connection" at the top of this oauth configuration, everything works fine. I am taken to a success page, and am able to view a token there.
Moving on to the code, I know teams is a bit funky with OAuth. My application is written in typescript using node and restify to handle the server aspect. My bot code, which extends the ActivityHandler, has the usual onMembersAdded, onMessage and onDialog functions. I've also added an onTokenResponseEvent and onUnrecognizedActvityType to handle the token response and invoke activity type.
this.onTokenResponseEvent(async (context, next) => {
console.log('Running dialog with Token Response Event Activity.');
await this.dialog.run(context, this.dialogState);
await next();
});
this.onUnrecognizedActivityType(async (context, next) => {
if (context.activity.type === ActivityTypes.Invoke) {
await this.dialog.run(context, this.dialogState);
}
await next();
});
Lastly, in my main dialog, I have created an oauth prompt (the connection name is also in the environment variables for the App Service):
this.addDialog(new OAuthPrompt(OAUTH_PROMPT, {
connectionName: process.env.connectionName,
text: 'Before we get started, could you please sign in?',
title: 'Sign In',
timeout: 300000
}));
This prompt is added to a waterfall dialog here:
this.addDialog(new WaterfallDialog(MAIN_WATERFALL_DIALOG, [
this.loginStep.bind(this),
this.confirmStep.bind(this),
this.projectStep.bind(this),
this.serviceStep.bind(this),
this.getServiceDialogStep.bind(this)
]));
This is the start of my bot. So when you interact with it, it will immediately ask you to login. Upon logging in, I can see the flow move to the confirm step, as I'm logging out the token there for debugging purposes.
I am using the Azure Devops Node API for hitting the Azure API's. I set this up with a bearer token auth handle based on the token response:
const authHandler = azdev.getBearerHandler(authToken);
this.connection = new azdev.WebApi(orgUrl, authHandler);
Unfortunately once the code moves to the project step, where it's trying to get the connection and projects, I am receiving a 401 error. The error is being thrown on getting the connection, so it never reaches the getProjects function:
this.coreApi = await this.connection.getCoreApi();
let projects = await this.coreApi.getProjects();
I have no clue what I might have setup wrong, but ideally I'd like to be able to hit these services with the logged in users credentials.
If it helps at all, the actual 401 error object that I get is this:
{
Error: TF400813: The user '' is not authorized to access this resource.
at RestClient.<anonymous> (/home/site/wwwroot/node_modules/typed-rest-client/RestClient.js:200:31)
at Generator.next (<anonymous>)
at fulfilled (/home/site/wwwroot/node_modules/typed-rest-client/RestClient.js:6:58)
at process._tickCallback (internal/process/next_tick.js:68:7)
statusCode: 401,
result: {
'$id': '1',
innerException: null,
message: 'TF400813: The user \'\' is not authorized to access this resource.',
typeName: 'Microsoft.TeamFoundation.Framework.Server.UnauthorizedRequestException, Microsoft.TeamFoundation.Framework.Server',
typeKey: 'UnauthorizedRequestException',
errorCode: 0,
eventId: 3000
}
}
That's pretty much an exhaustive description of my issue. If anyone can help I'd be so grateful! I hope I've provided enough details, but if I can provide anymore please let me know.
Upvotes: 0
Views: 1682
Reputation: 752
I don't know much about Azure Devops api, but likely you are using the incorrect token.
In the OAuth Connection Setting you set the scope to "openid", hence you get a token back targeted to Graph api as the audience, not Azure Devops rest api.
You can verify it by putting your token on jwt.ms, check the decoded aud
and scp
claims. It should have nothing to do with Azure Devops.
OAuth2 token only works towards the resource (audience) it's intended for. You cannot use a graph token to talk to Azure dev ops api.
Please try to update your "azureDevopsOauth" OAuth Connection Setting with the right Azure devops scope. Available scopes seem to be documented here, e.g. vso.build
instead of openid
if you want to read build artifacts. You can put multiple azure dev ops scopes separated by comma as the value:
vso.build,vso.code,vso.release
And please choose the least scope(s) you need to enable your scenario.
Once you do this, the OAuthPrompt should be throwing the user consent screen - double check the info there as well before you consent.
Upvotes: 1
Reputation: 1347
I think this has to do with the way that Teams handles actvities differently. Here is a bit more info from a Node sample:
At this stage the primary focus of this sample is how to use the Bot Framework support for oauth in your bot. The reason for prioritizing this is that Teams behaves slightly differently than other channels in this regard. Specifically an Invoke Activity is sent to the bot rather than the Event Activity used by other channels. This Invoke Activity must be forwarded to the dialog if the OAuthPrompt is being used. This is done by subclassing the ActivityHandler and this sample includes a reusable TeamsActivityHandler. This class is a candidate for future inclusion in the Bot Framework SDK.
My suggestion is to test this Node sample quickly to see if you can get it working in Teams. Then you should be able to take the approach from the Node sample and apply it to TypeScript (there currently not a sample for TypeScript).
Upvotes: 0