BotBuilder-MicrosoftTeams: Unable to authenticate with messaging extension

I’m attempting to authenticate with OAuth for my Microsoft Teams messaging extension. It also has a bot, although I don’t have that piece really functional yet and am first attempting authentication with the messaging extension. My messaging extension is fully functional if I use my authentication token pasted in from another authenticated website.

I’ve been working on this for days and have made zero progress.

The documentation page I found that seems the most promising is this one.

The first thing that I think is unclear is step 2:

Your service checks whether the user has first authenticated by inspecting the Teams user ID. What does this mean? I get the user ID and inspect it for what? Some cached authentication that is linked to the key? I assume this would be more clear once I get the other pieces working.

Step 4 is incorrect (or something is missing from the instructions):

The Microsoft Teams client launches a pop-up window hosting your webpage using the given authentication URL. When using an openUrl action, this opens a new browser tab in the default browser, not a pop-up.

Step 5 says

After the user signs in, you should close your window and send an “authentication code” to the Teams client. This is unclear on how you should get an authentication code, close the window, and how to send the code to Teams.

Is there not a missing step here where something within my service should be storing this authentication code with the user ID mentioned in step 2? This seems like the major missing piece where I cannot figure out what to do.

Step 6

The Teams client then reissues the query to your service I have yet to get this to work. I am guessing that this means that it will automatically call the same function that the auth response was returned from originally, although like I said above, I wouldn’t expect “inspect the user ID” to do anything different unless there’s some code somewhere that’s storing the auth code along with the user ID.

Below these summary steps, the documentation goes into more detail.

The Complete the sign-in flow section states:

When the sign-in request completes and redirects back to your page, it should perform the following steps:

  1. Generate a security code. (This can be a random number.) You need to cache this code on your service, along with the credentials obtained through the sign-in flow (such as OAuth 2.0 tokens).
  2. Call microsoftTeams.authentication.notifySuccess and pass the security code.

So I am supposed to generate a security code within my webpage, use the webpage to send this code and my auth token to my service, and call notifySuccess with the code? I think the “cache this code and the credentials on your service” should be another step, or I am misunderstanding what is supposed to be happening here.

The next bit says

At this point, the window closes and control is passed to the Teams client. The client now can reissue the original user query, along with the security code in the state property. When I call notifySuccess from my webpage, the window closes and my app now says “Something went wrong, please try again” in red below the “You’ll need to sign in to use this app” line. Nothing happens in my service, including the supposed reissue of the original query.

I have tried a lot of different things to try to have anything happen within my service after the authentication window closes and have been completely unsuccessful thus far. I think this means that I am using notifySuccess incorrectly, but it’s possible I just haven’t guessed the correct listener to try.

My code is based on the hello world sample, with the basic structure remaining the same (bot and messaging-extension files).

My messaging-extension.js file, with irrelevant pieces removed/turned into pseudocode:

module.exports.setup = function () {
  var builder = require('botbuilder');
  var teamsBuilder = require('botbuilder-teams');
  var bot = require('./bot');
  const https = require('https');

  // This works when I paste an authentication token from somewhere else
  const authToken = bot.connector.settings.authToken;

  bot.connector.onInvoke(function (event, callback) {
    console.log('messaging-extension.onInvoke');
  });

  // Constituent page link
  bot.connector.onAppBasedLinkQuery(function (event, query, callback) {
    console.log('messaging-extension.onAppBasedLinkQuery');
    // TODO check authentication
    let constituentId = 'parsed from link';
    var response = teamsBuilder.ComposeExtensionResponse
      .result('list')
      .attachments([
        getConstituentAttachment(constituentId)
      ])
      .toResponse();

    callback(null, response, 200);
  });

  // Constituent search
  bot.connector.onQuery('constituentSearch', function (event, query, callback) {
    console.log('messaging-extension.constituentSearch');

    // TODO inspect Teams user ID
    // if not authenticated
    let response = teamsBuilder.ComposeExtensionResponse.auth().actions([
        builder.CardAction.openUrl(null,
          'my webpage',
          'Please sign in')
    ]).toResponse();
    // I have also tried the following, which has the same behavior:
    // let response =  {
    //   "composeExtension":{
    //     "type":"auth",
    //     "suggestedActions":{
    //       "actions":[
    //         {
    //           "type": "openUrl",
    //           "value": "my webpage",
    //           "title": "Sign in to this app"
    //         }
    //       ]
    //     }
    //   }
    // };
    callback(null, response, 200);

    // else if authenticated
    // var searchText = 'parsed from query';
    // performConstituentSearch(searchText, callback);
  });

  function getConstituentAttachment(constituentId) {
    // Use the authToken to make an API call to get the constituent information based on the ID
    // Create a card attachment and return it
  }
};

bot.js:

module.exports.setup = function(app) {
  var builder = require('botbuilder');
  var teams = require('botbuilder-teams');
  var config = require('config');

  var connector = new teams.TeamsChatConnector({
    // settings
  });

  let callback = (err, result, statusCode) => {
    console.log('bot.callback');
  };

  connector.onSigninStateVerification((event, query, callback) => {
    console.log('bot.onSigninStateVerification');
  });

  connector.onInvoke((event, callback) => {
    console.log('bot.onInvoke');
  });

  var inMemoryBotStorage = new builder.MemoryBotStorage();

  var bot = new builder.UniversalBot(connector, function(session) {
    console.log('bot.UniversalBot');
  }).set('storage', inMemoryBotStorage);

  bot.on('receive', function (data) {
    console.log('bot.receive');
  });

  app.post('/api/messages', connector.listen());
  app.post('/api/composeExtension', connector.listen());
  module.exports.connector = connector;
};

The webpage I redirect to is an angular SPA where I have the user log in and my authentication service call notifySuccess:

import * as microsoftTeams from '@microsoft/teams-js';

@Injectable()
export class TeamsService {
  constructor(myTokenProvider) {
    microsoftTeams.initialize();
    this.myTokenProvider
      .getDecodedToken({ disableRedirect: true })
      .then((token) => {
        console.log('notifySuccess`);
        // TODO generate an ID and save the token on the server
        let id = 'random id';
        microsoftTeams.authentication.notifySuccess(id);
      }).catch(() => {
        console.log('Not logged in.');
      });
  }
}

My response in messaging-extension is based on the example code, teamBuilder.ComposeExtensionResponse.auth(). The code ComposeExtensionResponse.auth is found nowhere else on the internet. This example isn’t even complete, the code is actually commented out.

In the console logs for my service, I only see messaging-extension.constituentSearch one time, before I authenticate. The other console.logs were my attempts at seeing if something else was called when authenticated, but none of those have ever fired.

In my webpage console logs, I see only notifySuccess.

Finally, the section Reissued request example leads me to believe that my service needs to reissue the request, rather than what is said in step 6, “the Teams client then reissues the query to your service”.

I also found the page for authentication flow for bots, which seems to have a better explanation of what should happen, but I get lost on step 10, the invoke message with name = signin/verifyState never fires.

I found an old stackoverflow post that mentions

the microsoftTeams.authentication.authenticate(...) javascript function as described here although the link no longer links to a page that describes this function, I believe the link should go somewhere like this. This function is closer to what I would expect to use, which is specifically an authentication popup with a successCallback and failureCallback. This is client-side code so isn’t something you would call from a node.js service, but I expected something similar in the node.js SDK.

In summary, my problem seems to be that I have no idea how to listen for the authentication response, and there’s no clear documentation or examples explaining how to do so.

My setup:

  • MacOS Mojave 10.14.6
  • Microsoft Teams Version 1.2.00.8866
  • Node.js service, using ngrok locally
  • botbuilder 3.11.0
  • botbuilder-teams 0.2.6

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 22 (6 by maintainers)

Most upvoted comments

I found the solution and have gotten OAuth working with messaging extensions. Documenting the solution in the linked issue above.