BotBuilder-MicrosoftTeams-dotnet icon indicating copy to clipboard operation
BotBuilder-MicrosoftTeams-dotnet copied to clipboard

[Feature] Sample with messaging extension and OAuthPrompt

Open Blackbaud-ChristiSchneider opened this issue 6 years ago • 11 comments

I have a messaging extension (composeExtension/query) that requires authentication (it searches a 3rd party API). If the user attempts to search but is not logged in, I want to prompt the user to sign in, and once they have successfully signed in, run the search.

I am not sure how to do this, and I think a sample would be good to have since I think this is a common use case.

In case if you don't have access token (or login info), you can return next response:

image

VKAlwaysWin avatar Sep 26 '19 11:09 VKAlwaysWin

Thanks! How do you determine if you have the access token? In dialog, this is done by just sending an OAuthPrompt which passes a step context of TokenResponse to the next step if the user is already logged in, or if they aren't, prompts them and then passes it on a successful login. I don't understand how I could use a similar workflow in a messaging extension because there wouldn't be any "next step".

Also the example you gave has a direct link to the OAuth flow, but what I'm talking about is using the ConnectionName from app settings like what the OAuthPrompt uses.

In case if you use OAuth Connection setting, you can try something like that: first check token, then generate link.

image

VKAlwaysWin avatar Sep 26 '19 14:09 VKAlwaysWin

Unfortunately the token is always null when I have previously logged in. The example you're giving with the link does not use the oauth connection that OAuthPrompt provides, so authenticating in this way would not save the token so that it can be used elsewhere.

ITeamsContext is always null when running in Teams without using a magic code. When I run in the bot emulator using a magic code, it's not null. I can't test messaging extensions in the bot emulator, though.

Alright, I've got it narrowed down a little. I must be missing something in my Startup because ITeamsContext is always null, but everything in it is just a helper function anyway so I just copied out the pieces I need and got it partially working. If the user is already logged in due to a dialog conversation, the token is available within the composeExtension handler like the example you gave:

var adapter = (IUserTokenProvider)turnContext.Adapter;
var token = await adapter?.GetUserTokenAsync(turnContext, "MyConnectionName", null, cancellationToken);

However, if the user is not logged in when they trigger the composeExtension, the token is null in that code. I tried returning an auth type response and that did not work.

var link = await adapter?.GetOauthSignInLinkAsync(turnContext, "MyConnectionName", cancellationToken);
return new MessagingExtensionResponse
{
    ComposeExtension = new MessagingExtensionResult
    {
        Type = "auth",
        SuggestedActions = new MessagingExtensionSuggestedAction
        {
            Actions = new List<CardAction>
            {
                new CardAction
                {
                    Title = "You are not authorized. Please log in.",
                    Value = link,
                    Type = ActionTypes.OpenUrl
                }
            }
        }
    }
};

That code almost works. It displays "You are not authorized. Please log in. You'll need to sign in to use this app." in my message extension window where sign in is a link that opens the OAuth popup. I am able to sign in to the popup but signing in does not post a message to my endpoint; I would expect signin/verifyState here like I get when logging in via the dialog OAuthPrompt. Since the result of the oauth popup doesn't go anywhere, when the messaging extension is automatically retried, there is still no token.

I also tried the same code but with Type = ActionTypes.Signin and that doesn't work at all. It displays "Something went wrong. Please try again."

Hi @Blackbaud-ChristiSchneider,

Did you use https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-authentication?view=azure-bot-service-4.0&tabs=csharp%2Cbot-oauth#use-an-oauth-prompt-to-sign-the-user-in-and-get-a-token ? What provider do you use (facebook, Google, generic etc)?

ITeamsContext can be empty in some cases. Did you try code (with TeamsMiddleware middleware) in Teams ?

VKAlwaysWin avatar Sep 27 '19 14:09 VKAlwaysWin

Yes I used that page to create my app. However, I don't see how you would start a dialog from a messaging extension.

We have an in-house OAuth provider, so I registered it in Azure as "Generic Oauth 2".

I tried the middleware as shown in the README for this repo but ITeamsContext was always null when running the app inside of Teams, and was always not null when running in the Bot Framework Emulator. However, I have a workaround for that (copying out the relevant code) and this repository is going to be replaced so I won't put too much effort into diagnosing that.

Okay I figured out how to do this and will post a sample when I clean it up a little. I'm not sure why this works this way (I think this may be a miss somewhere) but signing in from the messaging extension does not trigger signin/verifystate. Instead, it includes the 6 digit magic code as the state property of the composeExtension/query. You can use this code to get a token.

var magicCodeObject = turnContext.Activity.Value as JObject;
var magicCode = magicCodeObject.GetValue("state", StringComparison.Ordinal)?.ToString();
var token = await adapter.GetUserTokenAsync(turnContext, _appSettings.ConnectionName, magicCode, cancellationToken).ConfigureAwait(false);

This also caches the token so that the following code returns the token in the future:

var adapter = (IUserTokenProvider)turnContext.Adapter;
var token = await adapter?.GetUserTokenAsync(turnContext, _appSettings.ConnectionName, null, cancellationToken);

Hi - we've got a PR with an example of this using the vNext of the Bot Framework SDK. You can check it out here: https://github.com/microsoft/botbuilder-dotnet/pull/2646

clearab avatar Oct 02 '19 16:10 clearab