Users have to sign in every time? - botframework

Right now, our users sign in every time they want to chat with our bot. It can be very annoying for them. Here's some information about the bot:
The bot is accessed using the Direct Line channel.
When I use the "Test in Web Chat" feature in Azure, I stay signed in every time. So it works fine there.
We are using the BotFramework-WebChat component for the UI.
I followed this tutorial to generate a user token. I generated a user ID like this:
var userId = "dl_" + new Random().Next() + new DateTime().Ticks;
Here is the code to authenticate users:
public GreetingDialog(IConfiguration configuration, IBotTelemetryClient telemetryClient)
: base(INTENTS.GREETING, configuration["ConnectionName"])
{
TelemetryClient = telemetryClient;
AddDialog(new OAuthPrompt(
nameof(OAuthPrompt),
new OAuthPromptSettings
{
ConnectionName = ConnectionName,
Text = "👋 Welcome! Please Sign In.",
Title = "Sign In",
Timeout = 30000,
})
{
TelemetryClient = telemetryClient
});
AddDialog(new WaterfallDialog(INTENTS.GREETING, new WaterfallStep[] {
PromptStepAsync,
GreetStepAsync,
})
{
TelemetryClient = telemetryClient
});
InitialDialogId = INTENTS.GREETING;
}
private async Task<DialogTurnResult> PromptStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
return await stepContext.BeginDialogAsync(nameof(OAuthPrompt), null, cancellationToken);
}
private async Task<DialogTurnResult> GreetStepAsync(WaterfallStepContext step, CancellationToken cancellationToken)
{
// ...
}

The problem is that every time you generate a user Id randomly, a new conversation is created. Being a new conversation, the bot cannot find the previous session.
If you want to do a test you can assign a static ID user:
var userId = "dl_123456789";
It shouldn't ask you to log in again.
To make a complete solution, the userId should be tied to some distinctive credentials in your application (ex: username/password)

Related

Skill bot sending proactive messages to a user

I have developed a bot which sends Proactive messages to user
I also cretaed bots which trigger skills.
I was trying to write something where a skills bot/Dialog would be able to send proactive message received via webhooks to the user and continue with the existing Skill dialog.
I am not able to quite understand that. If anyone could help me there.
I used the sample from here to create a simple Skill bot which saves the ConversationReference of the current Activity and calls a service in OnMessageActivityReceived()
// Save ConversationReference
var conversationReference = activity.GetConversationReference();
_conversationReferences.AddOrUpdate(conversationReference.User.Id, conversationReference, (key, newValue) => conversationReference);
// Calling external service
HttpResponseMessage responsemMsg = await client.PostAsync(RequestURI, stringContent);
if (responsemMsg.IsSuccessStatusCode)
{
var apiResponse = await responsemMsg.Content.ReadAsStringAsync();
//Post the API response to bot again
await turnContext.SendActivityAsync(MessageFactory.Text($"Message Sent {turnContext.Activity.Text}"), cancellationToken);
}
The called service eventually emits an event which calls an action in the NotifyController in my Skills Bot. It tries to grab the ConverstaionReference and send the activity using the TurnContext.
//I am using the Skill Bot Id for _appId parameter
await ((BotAdapter)_adapter).ContinueConversationAsync(_appId, conversationReference, async (context, token) =>
{
await context.SendActivityAsync(proMsg.eventName);
await context.SendActivityAsync(proMsg.context.ToString());
}, new System.Threading.CancellationToken());
This SendActivity fails and OnTurnError from the Skill Bot handles the exception.
I am not sure where excatly I am going wrong. I am new to the Bot framework learning.
My issue got resolved by using the ContinueConversation() overload with ClaimsIdentity and setting the right claims for audience, appid etc. It was basically authentication issue.
public virtual Task ContinueConversationAsync(ClaimsIdentity claimsIdentity, ConversationReference reference, string audience, BotCallbackHandler callback, CancellationToken cancellationToken);
This is how I created the ClaimsIdentity:
System.Collections.Generic.List<Claim> lclaim = new System.Collections.Generic.List<Claim>
{
new Claim(AuthenticationConstants.VersionClaim, "2.0"),
new Claim(AuthenticationConstants.AudienceClaim, <SkillBotId>),
new Claim(AuthenticationConstants.AppIdClaim, <SkillBotId>),
new Claim(AuthenticationConstants.AuthorizedParty, <SkillBotId>),
};
ClaimsIdentity ci = new ClaimsIdentity(lclaim);
async Task BotCallBack(ITurnContext turnContext, CancellationToken token)
{
<code to send activity back to parent bot>
}
await ((BotAdapter)this.botFrameworkHttpAdapter).ContinueConversationAsync(
ci,
conversationData.ConversationReference,
<ParentBotId>,
callback: BotCallBack,
default).ConfigureAwait(false);

Allow multiple Microsoft App IDs for chat bot

I have a chatbot that works on localhost, and it's working great. I then added a new Bot Channels Registration on Azure for testing, and that works fine too. I did it by taking its Microsoft App ID and password and putting it into my appsettings.json file.
However, I need to add another Bot Channels Registration. When I test it on that registration, my bot returns a 401 unauthorized error. It's because that has a new App ID and password. But I already put the App ID and password from my first registration channel. I need both of them to work.
How can I allow my chatbot to accept multiple App IDs and passwords? Or how do I get rid of that level of security completely (ie. Allow ALL App IDs and passwords)?
The answer, as #Mick suggested, is to create a bot adapter for each channel. You can do something like this if you want it really dynamic:
BotController.cs
[HttpPost, HttpGet]
public async Task PostAsync()
{
var credentialProvider = new SimpleCredentialProvider(YourAppId, YourAppPassword); // for each adapter
Adapter = new BotFrameworkHttpAdapter(credentialProvider); // for each adapter
await Adapter.ProcessAsync(Request, Response, Bot);
}
With a custom ICredentialProvider the appid and password can be retrieved from anywhere:
public class MultiCredentialProvider : ICredentialProvider
{
public Dictionary<string, string> Credentials = new Dictionary<string, string>
{
{ "YOUR_MSAPP_ID_1", "YOUR_MSAPP_PASSWORD_1" },
{ "YOUR_MSAPP_ID_2", "YOUR_MSAPP_PASSWORD_2" }
};
public Task<bool> IsValidAppIdAsync(string appId)
{
return Task.FromResult(this.Credentials.ContainsKey(appId));
}
public Task<string> GetAppPasswordAsync(string appId)
{
return Task.FromResult(this.Credentials.ContainsKey(appId) ? this.Credentials[appId] : null);
}
public Task<bool> IsAuthenticationDisabledAsync()
{
return Task.FromResult(!this.Credentials.Any());
}
}
Then, in Startup.cs:
services.AddSingleton<ICredentialProvider, MultiCredentialProvider>();

Bot Framework Proactive Message Passing Id Into Conversation State

I have a SMS / Twilio Channel that I'm using to send out a Proactive message to the user. To send the Proactive message I'm calling a API method passing in MySpecialId which is used later in the conversation.
I want to save this MySpecialId into the conversation but at the point I have the MySpecialId the conversation doesn't exist yet, and I don't have a turnContext, so I can't really save it yet.
Is there a way to pass this Id from my API method into the BotCallback? I created a quick example. (Here is the original example I'm using https://github.com/microsoft/BotBuilder-Samples/tree/master/samples/csharp_dotnetcore/16.proactive-messages)
Thanks
[HttpGet("{number}")]
public async Task<IActionResult> Get(string MySpecialId)
{
//For Twillio Channel
MicrosoftAppCredentials.TrustServiceUrl("https://sms.botframework.com/");
var NewConversation = new ConversationReference
{
User = new ChannelAccount { Id = $"+1{PHONENUMBERTOTEXTHERE}" },
Bot = new ChannelAccount { Id = "+1MYPHONENUMBERHERE" },
Conversation = new ConversationAccount { Id = $"+1{PHONENUMBERTOTEXTHERE}" },
ChannelId = "sms",
ServiceUrl = "https://sms.botframework.com/"
};
BotAdapter ba = (BotAdapter)_HttpAdapter;
await ((BotAdapter)_HttpAdapter).ContinueConversationAsync(_AppId, NewConversation, BotCallback, default(CancellationToken));
return new ContentResult()
{
Content = "<html><body><h1>Proactive messages have been sent.</h1></body></html>",
ContentType = "text/html",
StatusCode = (int)HttpStatusCode.OK,
};
}
private async Task BotCallback(ITurnContext turnContext, CancellationToken cancellationToken)
{
try
{
var MyConversationState = _ConversationState.CreateProperty<MyConversationData>(nameof(MyConversationData));
var ConversationState = await MyConversationState.GetAsync(turnContext, () => new MyConversationData(), cancellationToken);
//********************************************************************************************************************
ConversationState.MySpecialId = //HOW DO I GET MySpecialId FROM THE GET METHOD ABOVE HERE?
//********************************************************************************************************************
await _ConversationState.SaveChangesAsync((turnContext, false, cancellationToken);
await turnContext.SendActivityAsync("Starting proactive message bot call back");
}
catch (Exception ex)
{
this._Logger.LogError(ex.Message);
}
}
I don't believe that you can. Normally, you would do something like this by passing values through Activity.ChannelData, but ChannelData doesn't exist on ConversationReference.
Per comments below, #Ryan pointed out that you can pass data on ConversationAccount.Properties. Note, however, this is currently only available in the C# SDK. I've opened an issue to bring this into the Node SDK, but ETA is unknown at this point.
Instead, I'd suggest using a something more native to C#, like:
Create a ConcurrentDictionary
private ConcurrentDictionary<string, string> _idMap;
Map MySpecialId to Conversation.Id (in your Get function)
_idMap.AddOrUpdate(conversationReference.Conversation.Id, MySpecialId, (key, newValue) => MySpecialId);
Access the MySpecialId from the Activity.Conversation.Id (in BotCallback)
var ConversationState = await MyConversationState.GetAsync(turnContext, () => new MyConversationData(), cancellationToken);
ConversationState.MySpecialId = _idMap.GetValueOrDefault(turnContext.Activity.Conversation.Id);
Save ConversationState
await ConversationState.SaveChangesAsync(turnContext, false, cancellationToken);
There's other ways you could do this and some validation checks you'll need to add, but this should get you started.

Xamarin and Auth0 - getting refresh tokens

I was following the guide provided by auth0 and have been authenticating just fine, but I am getting tired of having to log in everytime I open the app and wanted to start storing and taking advantage of refresh tokens. However I can't seem to get a refresh token, its always null.
In my LoginActivity I have the following
_client = new Auth0Client(new Auth0ClientOptions
{
Domain = Resources.GetString(Resource.String.auth0_domain),
ClientId = Resources.GetString(Resource.String.auth0_client_id),
//Scope = "offline_access",
Activity = this
});
and handling the log in like so
_authorizeState = await _client.PrepareLoginAsync(new { audience = "myaudience.blahblahblah"});
protected override async void OnNewIntent(Intent intent)
{
base.OnNewIntent(intent);
var loginResult = await _client.ProcessResponseAsync(intent.DataString, _authorizeState);
var sb = new StringBuilder();
if (loginResult.IsError)
{
sb.AppendLine($"An error occurred during login: {loginResult.Error}");
}
else
{
var mainActivity = new Intent(this, typeof(MainActivity));
mainActivity.PutExtra("token", loginResult.AccessToken);
StartActivity(mainActivity);
Finish();
}
}
If I include the scope then I get an error back that the response doesn't contain an identity token. if I don't include I just don't get the refresh token.
For me, the trick were add Scope row as shown below.
The original code:
client = new Auth0Client(new Auth0ClientOptions
{
Domain = Resources.GetString(Resource.String.auth0_domain),
ClientId = Resources.GetString(Resource.String.auth0_client_id),
Activity = this
});
Changed and working one:
client = new Auth0Client(new Auth0ClientOptions
{
Domain = Resources.GetString(Resource.String.auth0_domain),
ClientId = Resources.GetString(Resource.String.auth0_client_id),
Activity = this,
Scope = "openid offline_access"
});
I tried only with this:
Scope = "offline_access"
But received an error, until the "openid" in the front of it.

sponsored messages on facebook with bot framework

How can I send a message to the user without the user sending me a message? Like for example CNN bot is sending messages every day in the morning by itself. How can I do that in the bot framework?
See this.
In fact, you do not strictly need to receive a message from the user first, but addressing manually can be error-prone (you have to know the user's and bot's channel account, the service URL, etc.)
And in turn (per #thegaram's message), that only works for some channels. For example, Skype requires that the user contact the bot before the bot can message the user.
Once contacted, you can store the user's channelAccount data once they contact you and use that to send them proactive messages. For example if the user has subscribed to hear sports scores for a particular team over time.
Any sort of unsolicited spam messages of course are prohibited by the policies of the Bot Framework (and most of the channels).
Yes you can do that. We called it Greeting from Bot. I have done it and sharing a sample code with you.
Write this code in your messageController or first dialog used in bot.
if (activity.Text == null)
{
ConnectorClient connector = new ConnectorClient(new Uri(activity.ServiceUrl));
Activity isActivityTyping = activity.CreateReply();
isActivityTyping.Type = ActivityTypes.Typing;
await connector.Conversations.ReplyToActivityAsync(isActivityTyping);
await Conversation.SendAsync(activity, () => new Dialogs.GreetDialog());
}
after this code you need to create a dialog GreetDialog. Below is the cs file code for your reference.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;
namespace GPP.Bot.Dialogs
{
[Serializable]
internal class GreetDialog : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
context.Wait(Greeting);
}
private async Task Greeting(IDialogContext context, IAwaitable<IMessageActivity> argument)
{
var message = await argument;
if (string.IsNullOrEmpty(message.Text))
{
// Hero Card
var cardMsg = context.MakeMessage();
var attachment = BotWelcomeCard("Hello, I am a bot. Right now I am on training and in a prototype state", "");
cardMsg.Attachments.Add(attachment);
await context.PostAsync(cardMsg);
context.Call<object>(new ActionDialog(), AfterGreetingDialogCompleted);
}
else
{
context.Call<object>(new ActionDialog(), AfterGreetingDialogCompleted);
}
}
private static Attachment BotWelcomeCard(string responseFromQNAMaker, string userQuery)
{
var heroCard = new HeroCard
{
Title = userQuery,
Subtitle = "",
Text = responseFromQNAMaker,
Images = new List<CardImage> { new CardImage("https://i2.wp.com/lawyerist.com/lawyerist/wp-content/uploads/2016/08/docubot.gif?fit=322%2C294&ssl=1") },
Buttons = new List<CardAction> { new CardAction(ActionTypes.ImBack, "Show Menu", value: "Show Bot Menu") }
};
return heroCard.ToAttachment();
}
private async Task AfterGreetingDialogCompleted(IDialogContext context, IAwaitable<object> result)
{
context.Done<object>(new object());
}
}
}
this is a working code. Do let me know in case you face ant issue.
~cheers :)

Resources