microsoft / botframework-sdk

Bot Framework provides the most comprehensive experience for building conversation applications.
MIT License
7.48k stars 2.44k forks source link

Bot authentication returns 401 following load testing instructions #3510

Closed DrWala closed 6 years ago

DrWala commented 6 years ago

Bot Info

Issue Description

I'm attempting to load test my bot using the instructions found here: https://blog.botframework.com/2017/06/19/load-testing-a-bot/ but keep getting a 401 when posting to my bot with the access token.

I attempted to generate an access token by POSTing to https://login.microsoftonline.com/botframework.com/oauth2/v2.0/token with the following header values:

grant_type:client_credentials
client_id:9c0072eb-f501-4042-af87-353a7dc8aee9
client_secret:app_secret
scope:9c0072eb-f501-4042-af87-353a7dc8aee9/.default

The request returned the generated access token, but I'm unable to send anything to my bot's URL. I'm sending a POST to https://mybotname.azurewebsites.net/api/messages with the following headers

Content-Type:application/json
Authorization:bearer mytoken

and a sample JSON payload as follows

{
    "type": "message",
    "id": "1234",
    "channelId" : "test", 
    "conversation": { "id": "1122" },
    "from": { "id": "user_id" },
    "recipient": { "id": "spjaebot" },
    "text":"hi",
    "serviceUrl": "https://mytunnel.ngrok.io" 
}

This results in a 401 with the error message of "BotAuthenticator failed to authenticate incoming request!". However, I can test my bot through direct line, web chat, emulator (both local and remote) as well as telegram. I'm guessing I am doing something wrong with regards to authentication. I've set up a message sink using ngrok for load testing purposes. I don't think it is an issue with the payload as the bot doesn't even authenticate the request in the first place. I've also tried posting to /api/messages/v3 to no avail.

If any more information is needed I'm happy to send it over. Would appreciate any help :)

DrWala commented 6 years ago

Just an update. For some reason the access token now works. However I'm getting this exception:

Error: Unable to deserialize the response.

Trace: at Microsoft.Bot.Connector.Conversations.d8.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.Bot.Connector.ConversationsExtensions.d7.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(Task task) at NyxBot.Bot.MainBot.d__0.MoveNext() in X:\Nyx Intelligence LLP\Custom Projects\Nyx-SP\NyxBot\NyxBot\Bot\MainBot.cs:line 72

Source: Microsoft.Bot.Connector

Key Data: System.Collections.ListDictionaryInternal

This looks very much like some of the existing issues such as #618 and a few other existing stackoverflow posts. I understand that it isn't my place to ask but this task is blocking some other things at the moment, and I'm sort of in need of help fast. If that is possible I would really really appreciate it.

JasonSowers commented 6 years ago

Looking into this, thanks for reporting

DrWala commented 6 years ago

Thanks @JasonSowers

I've got some new information which may or may not help. I can reply to normal messages, but that error seems to only occur when responding with a typing activity. The activity is created as such:

ConnectorClient connector = new ConnectorClient(new Uri(message.ServiceUrl));
Activity typing = message.CreateReply();
typing.Type = ActivityTypes.Typing;
typing.Text = null;
await connector.Conversations.ReplyToActivityAsync(typing);

where message is the incoming activity.

The error occurs when the last line await connector.Conversations.ReplyToActivityAsync(typing); is being executed, throwing the exception in my post earlier.

JasonSowers commented 6 years ago

What about other activity types is typing the only one throwing the error? Still working on getting a version up and running, feel free to share you mock channel if you can.

DrWala commented 6 years ago

From what I see, the error is only thrown only when responding with a typing activity and when I do a manual post to the bot. If I use the existing channels such as Telegram/Web chat/Direct Line, it doesn't throw any errors. I am able to get a typing indicator as well as the expected response

I will test with IActivity typing = message.CreateReply().AsTypingActivity(); and get back to you. My mock channel is merely an ngrok tunnel to my own machine, so it won't always be up haha

DrWala commented 6 years ago

Just tested with IActivity typing = message.CreateReply().AsTypingActivity(). It doesnt send the typing indicator at all.

JasonSowers commented 6 years ago

We are trying pretty hard to reproduce still, but running into our own issues 👎. This is going to end up being a blog post with samples when all is said and done. Again, thanks for reporting this.

DrWala commented 6 years ago

Hahahaha thank you @JasonSowers, appreciate the work you guys put in. If you need any more info I'm more than happy to test/help. In the meantime, I've disabled typing when I load test and for some reason the auth tokens work now - which is weird but I'm not complaining.

If I may suggest, could BF eventually have a default message sink for load tests? 😜

DrWala commented 6 years ago

Hey guys, any news?

JasonSowers commented 6 years ago

@DrWala Sorry for the extremely delayed response I was out of town then was working on a project. I am unable to reproduce as when I send the typing activity below there is no error:

        public static Activity Typing()
        {
            Activity a = new Activity
            {
                Type = ActivityTypes.Typing,
                Id = "9dn3fa6lh4hd9dn3fa6lh4hd",
                ChannelId = Microsoft.Bot.Builder.Dialogs.ChannelIds.Console,
                Conversation = new ConversationAccount(id: "9dn3fa6lh4hd"),
                From = new ChannelAccount(id: "user", name: "username"),
                Recipient = new ChannelAccount(id: "bot", name: "botname"),
                ServiceUrl = @"http://localhost:55086/api/values",
                MembersAdded = new List<ChannelAccount>(),
                MembersRemoved = new List<ChannelAccount>(),
                Locale = "en-US",
                Attachments = new List<Attachment>(),
                ReplyToId = "nii4344blg42",
                TextFormat = "plain",
                Timestamp = DateTime.Now,
                ChannelData = JsonConvert.SerializeObject(new { clientActivityId = "1506483656068.11949484894092266.2" })
            };

            return a;
        }

I am sending the activity like this:

            var token = GetToken();
            var jsonActivity = JsonConvert.SerializeObject(ActivityGenerator.Typing());

            using (var client = new WebClient())
            {
                client.Headers.Add("Content-Type", "application/json");
                client.Headers.Add("Authorization", $"Bearer {token}");
                var response = client.UploadString("https://80e5a947.ngrok.io/api/messages", jsonActivity);
                //Console.WriteLine(response);
            }

I'll dive back into this today and see if I can reproduce with the way you are creating and sending the message. I created this class below to generate all types of activities. It may or may not help you, but i figured I would share. I know it probably needs some edits to be fully useful but it's a good start:

    public static class ActivityGenerator
    {

        public static Activity Message()
        {
            Activity a = new Activity
            {
                Type = ActivityTypes.Message,
                Id = "9dn3fa6lh4hd9dn3fa6lh4hd",
                ChannelId = Microsoft.Bot.Builder.Dialogs.ChannelIds.Console,
                Conversation = new ConversationAccount(id: "9dn3fa6lh4hd"),
                From = new ChannelAccount(id: "user", name: "username"),
                Recipient = new ChannelAccount(id: "bot", name: "botname"),
                Text = "Legit Nachos",
                ServiceUrl = @"http://localhost:55086/api/values",
                MembersAdded = new List<ChannelAccount>(),
                MembersRemoved = new List<ChannelAccount>(),
                Locale = "en-US",
                Attachments = new List<Attachment>(),
                ReplyToId = "nii4344blg42",
                TextFormat = "plain",
                Timestamp = DateTime.Now,
                ChannelData = JsonConvert.SerializeObject(new {clientActivityId = "1506483656068.11949484894092266.2"})
            };

            return a;
        }
        public static Activity ConversationUpdate()
        {
            Activity a = new Activity();

            a.Type = ActivityTypes.ConversationUpdate;
            a.Id = "9dn3fa6lh4hd9dn3fa6lh4hd";
            a.ChannelId = Microsoft.Bot.Builder.Dialogs.ChannelIds.Console;
            a.Conversation = new ConversationAccount(id: "9dn3fa6lh4hd");
            a.From = new ChannelAccount(id: "user", name: "username");
            a.Recipient = new ChannelAccount(id: "bot", name: "botname");
            a.Text = "Legit Nachos";
            a.ServiceUrl = @"http://localhost:55086/api/values";
            a.MembersAdded = new List<ChannelAccount>();
            a.MembersRemoved = new List<ChannelAccount>();
            a.Locale = "en-US";
            a.Attachments = new List<Attachment>();
            a.ReplyToId = "nii4344blg42";
            a.TextFormat = "plain";
            a.Timestamp = DateTime.Now;
            a.ChannelData = JsonConvert.SerializeObject(new { clientActivityId = "1506483656068.11949484894092266.2" });

            return a;
        }

        public static Activity ContactRelationUpdate()
        {
            Activity a = new Activity
            {
                Type = ActivityTypes.ContactRelationUpdate,
                Id = "9dn3fa6lh4hd9dn3fa6lh4hd",
                ChannelId = Microsoft.Bot.Builder.Dialogs.ChannelIds.Console,
                Conversation = new ConversationAccount(id: "9dn3fa6lh4hd"),
                From = new ChannelAccount(id: "user", name: "username"),
                Recipient = new ChannelAccount(id: "bot", name: "botname"),
                Text = "Legit Nachos",
                ServiceUrl = @"http://localhost:55086/api/values",
                MembersAdded = new List<ChannelAccount>(),
                MembersRemoved = new List<ChannelAccount>(),
                Locale = "en-US",
                Attachments = new List<Attachment>(),
                ReplyToId = "nii4344blg42",
                TextFormat = "plain",
                Timestamp = DateTime.Now,
                ChannelData = JsonConvert.SerializeObject(new { clientActivityId = "1506483656068.11949484894092266.2" })
            };

            return a;
        }

        public static Activity DeleteUserData()
        {
            Activity a = new Activity
            {
                Type = ActivityTypes.DeleteUserData,
                Id = "9dn3fa6lh4hd9dn3fa6lh4hd",
                ChannelId = Microsoft.Bot.Builder.Dialogs.ChannelIds.Console,
                Conversation = new ConversationAccount(id: "9dn3fa6lh4hd"),
                From = new ChannelAccount(id: "user", name: "username"),
                Recipient = new ChannelAccount(id: "bot", name: "botname"),
                Text = "Legit Nachos",
                ServiceUrl = @"http://localhost:55086/api/values",
                MembersAdded = new List<ChannelAccount>(),
                MembersRemoved = new List<ChannelAccount>(),
                Locale = "en-US",
                Attachments = new List<Attachment>(),
                ReplyToId = "nii4344blg42",
                TextFormat = "plain",
                Timestamp = DateTime.Now,
                ChannelData = JsonConvert.SerializeObject(new { clientActivityId = "1506483656068.11949484894092266.2" })
            };

            return a;
        }

        public static Activity EndOfConversation()
        {
            Activity a = new Activity
            {
                Type = ActivityTypes.EndOfConversation,
                Id = "9dn3fa6lh4hd9dn3fa6lh4hd",
                ChannelId = Microsoft.Bot.Builder.Dialogs.ChannelIds.Console,
                Conversation = new ConversationAccount(id: "9dn3fa6lh4hd"),
                From = new ChannelAccount(id: "user", name: "username"),
                Recipient = new ChannelAccount(id: "bot", name: "botname"),
                Text = "Legit Nachos",
                ServiceUrl = @"http://localhost:55086/api/values",
                MembersAdded = new List<ChannelAccount>(),
                MembersRemoved = new List<ChannelAccount>(),
                Locale = "en-US",
                Attachments = new List<Attachment>(),
                ReplyToId = "nii4344blg42",
                TextFormat = "plain",
                Timestamp = DateTime.Now,
                ChannelData = JsonConvert.SerializeObject(new { clientActivityId = "1506483656068.11949484894092266.2" })
            };

            return a;
        }

        public static Activity Event()
        {
            Activity a = new Activity
            {
                Type = ActivityTypes.Event,
                Id = "9dn3fa6lh4hd9dn3fa6lh4hd",
                ChannelId = Microsoft.Bot.Builder.Dialogs.ChannelIds.Console,
                Conversation = new ConversationAccount(id: "9dn3fa6lh4hd"),
                From = new ChannelAccount(id: "user", name: "username"),
                Recipient = new ChannelAccount(id: "bot", name: "botname"),
                Text = "Legit Nachos",
                ServiceUrl = @"http://localhost:55086/api/values",
                MembersAdded = new List<ChannelAccount>(),
                MembersRemoved = new List<ChannelAccount>(),
                Locale = "en-US",
                Attachments = new List<Attachment>(),
                ReplyToId = "nii4344blg42",
                TextFormat = "plain",
                Timestamp = DateTime.Now,
                ChannelData = JsonConvert.SerializeObject(new { clientActivityId = "1506483656068.11949484894092266.2" })
            };

            return a;
        }

        public static Activity InstallationUpdate()
        {
            Activity a = new Activity
            {
                Type = ActivityTypes.InstallationUpdate,
                Id = "9dn3fa6lh4hd9dn3fa6lh4hd",
                ChannelId = Microsoft.Bot.Builder.Dialogs.ChannelIds.Console,
                Conversation = new ConversationAccount(id: "9dn3fa6lh4hd"),
                From = new ChannelAccount(id: "user", name: "username"),
                Recipient = new ChannelAccount(id: "bot", name: "botname"),
                Text = "Legit Nachos",
                ServiceUrl = @"http://localhost:55086/api/values",
                MembersAdded = new List<ChannelAccount>(),
                MembersRemoved = new List<ChannelAccount>(),
                Locale = "en-US",
                Attachments = new List<Attachment>(),
                ReplyToId = "nii4344blg42",
                TextFormat = "plain",
                Timestamp = DateTime.Now,
                ChannelData = JsonConvert.SerializeObject(new { clientActivityId = "1506483656068.11949484894092266.2" })
            };

            return a;
        }
        public static Activity Invoke()
        {
            Activity a = new Activity
            {
                Type = ActivityTypes.Invoke,
                Id = "9dn3fa6lh4hd9dn3fa6lh4hd",
                ChannelId = Microsoft.Bot.Builder.Dialogs.ChannelIds.Console,
                Conversation = new ConversationAccount(id: "9dn3fa6lh4hd"),
                From = new ChannelAccount(id: "user", name: "username"),
                Recipient = new ChannelAccount(id: "bot", name: "botname"),
                Text = "Legit Nachos",
                ServiceUrl = @"http://localhost:55086/api/values",
                MembersAdded = new List<ChannelAccount>(),
                MembersRemoved = new List<ChannelAccount>(),
                Locale = "en-US",
                Attachments = new List<Attachment>(),
                ReplyToId = "nii4344blg42",
                TextFormat = "plain",
                Timestamp = DateTime.Now,
                ChannelData = JsonConvert.SerializeObject(new { clientActivityId = "1506483656068.11949484894092266.2" })
            };

            return a;
        }

        public static Activity MessageReaction()
        {
            Activity a = new Activity
            {
                Type = ActivityTypes.MessageReaction,
                Id = "9dn3fa6lh4hd9dn3fa6lh4hd",
                ChannelId = Microsoft.Bot.Builder.Dialogs.ChannelIds.Console,
                Conversation = new ConversationAccount(id: "9dn3fa6lh4hd"),
                From = new ChannelAccount(id: "user", name: "username"),
                Recipient = new ChannelAccount(id: "bot", name: "botname"),
                Text = "Legit Nachos",
                ServiceUrl = @"http://localhost:55086/api/values",
                MembersAdded = new List<ChannelAccount>(),
                MembersRemoved = new List<ChannelAccount>(),
                Locale = "en-US",
                Attachments = new List<Attachment>(),
                ReplyToId = "nii4344blg42",
                TextFormat = "plain",
                Timestamp = DateTime.Now,
                ChannelData = JsonConvert.SerializeObject(new { clientActivityId = "1506483656068.11949484894092266.2" })
            };

            return a;
        }
        public static Activity Ping()
        {
            Activity a = new Activity
            {
                Type = ActivityTypes.Ping,
                Id = "9dn3fa6lh4hd9dn3fa6lh4hd",
                ChannelId = Microsoft.Bot.Builder.Dialogs.ChannelIds.Console,
                Conversation = new ConversationAccount(id: "9dn3fa6lh4hd"),
                From = new ChannelAccount(id: "user", name: "username"),
                Recipient = new ChannelAccount(id: "bot", name: "botname"),
                Text = "Legit Nachos",
                ServiceUrl = @"http://localhost:55086/api/values",
                MembersAdded = new List<ChannelAccount>(),
                MembersRemoved = new List<ChannelAccount>(),
                Locale = "en-US",
                Attachments = new List<Attachment>(),
                ReplyToId = "nii4344blg42",
                TextFormat = "plain",
                Timestamp = DateTime.Now,
                ChannelData = JsonConvert.SerializeObject(new { clientActivityId = "1506483656068.11949484894092266.2" })
            };

            return a;
        }

        public static Activity Typing()
        {
            Activity a = new Activity
            {
                Type = ActivityTypes.Typing,
                Id = "9dn3fa6lh4hd9dn3fa6lh4hd",
                ChannelId = Microsoft.Bot.Builder.Dialogs.ChannelIds.Console,
                Conversation = new ConversationAccount(id: "9dn3fa6lh4hd"),
                From = new ChannelAccount(id: "user", name: "username"),
                Recipient = new ChannelAccount(id: "bot", name: "botname"),
                ServiceUrl = @"http://localhost:55086/api/values",
                MembersAdded = new List<ChannelAccount>(),
                MembersRemoved = new List<ChannelAccount>(),
                Locale = "en-US",
                Attachments = new List<Attachment>(),
                ReplyToId = "nii4344blg42",
                TextFormat = "plain",
                Timestamp = DateTime.Now,
                ChannelData = JsonConvert.SerializeObject(new {clientActivityId = "1506483656068.11949484894092266.2"})
            };

            return a;
        }
    }
JasonSowers commented 6 years ago

is there any more code in this method, also where are you calling this method from? I am still unable to reproduce sending a typing activity like below:

ConnectorClient connector = new ConnectorClient(new Uri(message.ServiceUrl));
Activity typing = message.CreateReply();
typing.Type = ActivityTypes.Typing;
typing.Text = null;
await connector.Conversations.ReplyToActivityAsync(typing);
DrWala commented 6 years ago

Hi @JasonSowers - So as mentioned earlier the typing activity only messes up when I'm doing the load test following instructions from the BF blog. Under normal situations, it throws no errors and workes seamlessly. So that's kinda odd.

Regarding what else is in that method, the flow is essentially: recieve activity -> check if type is message -> send typing activity -> process message -> send response.

During load testing, if I comment out the send typing bit, I don't get any errors. For the purposes of the load test I wrapped it in a try catch :P

To reporoduce the error, try following the load test instructions at https://blog.botframework.com/2017/06/19/load-testing-a-bot/ and see if returning a typing activity to your sink causes an error.

Regarding the creation of a typing activity altogether, that's odd as the following code returns a typing response on fb, telegram, web chat and direct line

ConnectorClient connector = new ConnectorClient(new Uri(message.ServiceUrl));
Activity typing = message.CreateReply();
typing.Type = ActivityTypes.Typing;
typing.Text = null;
await connector.Conversations.ReplyToActivityAsync(typing);
JasonSowers commented 6 years ago

@DrWala are you at all getting a 415 error "unsupported media type"? I was able to produce this error so far. The flow you are talking about is it... BOT receive activity -> check if type is message -> send typing activity -> process message -> send response.
or Mock Channel receive activity -> check if type is message -> send typing activity -> process message -> send response.

DrWala commented 6 years ago

@JasonSowers I got the 415 when I missed a content type header only.

Regarding the flow, its technically the mock channel receiving the activity I suppose. What is your definition of Bot vs mock channel?

JasonSowers commented 6 years ago

The bot is the bot itself, the mock channel is what receives the message from the bot.

DrWala commented 6 years ago

I set up a message sink using a node server. So I would do a direct post to the bot's endpoint (localhost:3979), it would then receive activity -> check if type is message -> send typing activity -> process message -> send response (to message sink).

Given that's the case, then the flow I'm talking about is the former - Bot receive activity -> ... etc.

JasonSowers commented 6 years ago

I really wish I had a better answer for you but I am just unable to reproduce the Unauthorized 401. Maybe you could put what you have in a repo for us to take a look?

DrWala commented 6 years ago

Hi @JasonSowers Give me some time and I will recreate the project I had. Been a busy week, but will get back to you soon

JasonSowers commented 6 years ago

Sounds good, please @ mention me as I have been working on other projects lately and have not been checking GitHub daily.

alundiak commented 6 years ago

@DrWala why do u use https://mybotname.azurewebsites.net/api/messages if in docs, it's said to use

Question2 : From where u took http://localhost:55086/api/values ? So far I kinda know what /api/messages fro, but /values? SOmething new. I did not see in docs. Can u point me to docs URL?

PS. I kind stuck on this stage - I want to send simple AJAX call to Bot API so that API triggers skype channel message. I have either 401 or CORS with MS server, and I don't get why and what to do next.

DrWala commented 6 years ago

@JasonSowers My schedule has opened up now, I'll restart my testing of this on Monday. Will keep you updated. I'm really sorry for making you wait on this.

@alundiak The first POST request you typed above is the same one I use to get the token. The second GET request is probably only useful if you are using OpenID to authenticate requests from bot connector to your bot, which I personally think is too much hassle.

Additionally, I'm quite sure https://smba.trafficmanager.net/apis/v3/conversations/${conversationId}/activities is a sample bot url, which can be replaced with any bot url you have.

Lastly, /api/values is probably a custom controller endpoint which @JasonSowers is using to test as a message sink.

If I'm wrong about any of this do let me know :)

JasonSowers commented 6 years ago

Going to close this for inactivity let me know if you are still having issues

DrWala commented 6 years ago

Hi @JasonSowers, no issues on my end now. Will reopen if need be.

KetanVidhate commented 6 years ago

can any one help me with how to create message sink in c#? and from where I would get Conversation ID?

JasonSowers commented 6 years ago

Are you using c# or node @KetanVidhate

KetanVidhate commented 6 years ago

c#

JasonSowers commented 6 years ago

Just create a web API project and hit the endpoint. I just used the default valuesController that was generated and modified the post method. you will need to add the Bot.Builder package to the project.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using Microsoft.Bot.Connector;
using Newtonsoft.Json;

namespace MockChannel.Controllers
{

    public class ValuesController : ApiController
    {

        public HttpResponseMessage Post([FromBody]Activity value)
        {
            ConnectorClient connector = new ConnectorClient(new Uri(value.ServiceUrl));
            Activity typing = value.CreateReply();
            typing.Type = ActivityTypes.Typing;
            typing.Text = null;
            connector.Conversations.ReplyToActivityAsync(typing);

            Activity a = new Activity
            {
                Type = ActivityTypes.Message,
                Id = "9dn3fa6lh4hd9dn3fa6lh4hd",
                ChannelId = Microsoft.Bot.Builder.Dialogs.ChannelIds.Console,
                Conversation = new ConversationAccount(id: "9dn3fa6lh4hd"),
                From = new ChannelAccount(id: "user", name: "username"),
                Recipient = new ChannelAccount(id: "bot", name: "botname"),
                Text = "Mock Channel",
                ServiceUrl = @"http://localhost:55086/api/values",
                MembersAdded = new List<ChannelAccount>(),
                MembersRemoved = new List<ChannelAccount>(),
                Locale = "en-US",
                Attachments = new List<Attachment>(),
                ReplyToId = "nii4344blg42",
                TextFormat = "plain",
                Timestamp = DateTime.Now,
                ChannelData = JsonConvert.SerializeObject(new { clientActivityId = "1506483656068.11949484894092266.2" })
            };

            connector.Conversations.SendToConversationAsync(a);
            var response = Request.CreateResponse(HttpStatusCode.OK);
            return response;
        }
    }
}
eflorespalma commented 6 years ago

@DrWala How did you solve the problem with the post api/messages.... I have exactly the same problem trying to use load testing to my bot.... everything runs ok but in the last part i get a 401 status code.