Highlights of the article series
- How to register an app with Azure Active Directory?
- How to get the access token using Azure Active Directory authentication?
- How to register a Chabot application with Azure Bot Service?
- How to user Bot State Service to save user conversation state data such as access token?
Prerequisite
You can pull code from
GitHub.
In the previous part of the article series, we have completed the initial setup such as app registration, Azure Active Directory Authentication and saving AccessToken to Bot State Service. In this part of the article, we will implement chatbot which will get the Access Token from Bot State Service and make Azure service call.
Request flow
In this diagram, I have mentioned sequence numbers to identify the flow.
Step 1
The user will start a conversation with abot by greeting it; i.e., saying ‘Hi’ or ‘Hello’.
Step 2
We have already developed a bot application to consume LUIS service in an article
Chat Bot Using Microsoft Bot Framework With LUIS - Part Two. We will reuse it for further development. For Azure related communication, we will add
AzureController and
AzureDialog in our bot application. We have also added Images folder and
ChatBot.png to it.
AzureController.cs
I have updated Post() method to invoke AzureDialog() with parameter activity and a relative path to access resources under bot app such as images. Whenever the user enters a message we are sending typing indicator before calling AzureDialog.
- public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
- {
- if (activity.Type == ActivityTypes.Message)
- {
- ConnectorClient connector = new ConnectorClient(new System.Uri(activity.ServiceUrl));
- Activity isTypingReply = activity.CreateReply("Bot is typing...");
- isTypingReply.Type = ActivityTypes.Typing;
- await connector.Conversations.ReplyToActivityAsync(isTypingReply);
-
- string resourcesPath = this.Url.Request.RequestUri.AbsoluteUri.Replace(@"api/azure", "");
- await Conversation.SendAsync(activity, () => new Dialogs.AzureDialog(activity, resourcesPath));
- }
- else
- {
- HandleSystemMessage(activity);
- }
- var response = Request.CreateResponse(HttpStatusCode.OK);
- return response;
- }
AzureDialog.cs
I have modified GreetWelcome method, which will create StateClient object for our bot app and get user data as per channel id and user id. We get access token using GetProperty() method. If the access token is empty then we will prompt the user with SIGN IN URL. We are using thumbnail card to show welcome text and bot image. If a user is logged in already then access token will be available in user data. Then we will call validate method against access token. It will return ClaimsPrincipal. As we are working on the development environment, I put localhost web application value as authentication URL. This URL is targeted to action ‘LoginWithAzure’ of ‘HomeController'. We will pass channel id and user id as query string parameter to save token against it after login.
- [LuisIntent("Greet.Welcome")]
- public async Task GreetWelcome(IDialogContext context, LuisResult luisResult)
- {
- StateClient stateClient = new StateClient(new MicrosoftAppCredentials("BOT_APP_ID", "BOT_APP_CLIENT_SECRET"));
-
- BotData userData = stateClient.BotState.GetUserData(context.Activity.ChannelId, context.Activity.From.Id);
-
- string accesstoken = userData.GetProperty<string>("AccessToken");
-
- if (string.IsNullOrEmpty(accesstoken))
- {
- string loginUrl = $"https://localhost:44332/home/LoginWithAzure?channelId={this._channel}&userId={this._user}";
- var message = context.MakeMessage();
-
- ThumbnailCard thumbnailCard = new ThumbnailCard();
- thumbnailCard.Title = $"Good morning dude..";
- thumbnailCard.Subtitle = "Sign in with Azure \U0001F511";
- thumbnailCard.Buttons = new List<CardAction>();
- thumbnailCard.Buttons.Add(new CardAction()
- {
- Value = $"{loginUrl}",
- Type = "signin",
- Title = "Sign In"
- });
- thumbnailCard.Images = new List<CardImage>();
- thumbnailCard.Images.Add(new CardImage($"{this._resourcesPath}/Images/ChatBot.png"));
-
- message.Attachments = new List<Attachment>();
- message.Attachments.Add(thumbnailCard.ToAttachment());
- await context.PostAsync(message);
- }
- else
- {
- ClaimsPrincipal jwtToken = await Helper.Validate(accesstoken);
- await context.PostAsync($"Logged in as {jwtToken.Identity.Name}");
- }
-
- context.Wait(this.MessageReceived);
- }
Step 3
When a user clicks on the link, control will go to LoginWithAzure action method in Home controller of our MVC web app. We will get channel id and user id from query string parameter. We will save it to bot service state for future use.
Step 4
We will form O365 authentication URL using tenant id and app id and redirect URI. Navigate the user to login page. I have provided redirect URI as action method LoggedinToAzure of Home controller. For code level details, refer Chat Bot With Azure Authentication - Part One.
Step 5
After successful login, ACS will return control to Redirect URI i.e. in LoggedinToAzure action method. ACS will make post call to redirect the URI and send Authorization code as Form parameter.
Step 6
Our application will get access token using authorization code and save access token in bot state service against channel id and user id and ask the user to continue with chat.
Step 7
After successful login user comes back to chat window and starts a conversation with bot. Let’s say, the user will say Hello to bot.
Step 8
This time bot will get access token in bot state service for current channel and user. The bot will call validate() method to get ClaimsPrincipal from access token.
- public static async Task<ClaimsPrincipal> Validate(string accessToken)
- {
- string stsDiscoveryEndpoint = "https://login.microsoftonline.com/AZURE_TENANT_ID/.well-known/openid-configuration";
-
- ConfigurationManager<OpenIdConnectConfiguration> configManager = new Microsoft.IdentityModel.Protocols.ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint);
-
- OpenIdConnectConfiguration config = await configManager.GetConfigurationAsync();
-
- System.IdentityModel.Tokens.TokenValidationParameters validationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
- {
- ValidIssuer = config.Issuer,
- ValidateAudience = false,
- IssuerSigningTokens = config.SigningTokens,
- CertificateValidator = X509CertificateValidator.None
- };
-
- JwtSecurityTokenHandler tokendHandler = new JwtSecurityTokenHandler();
-
- System.IdentityModel.Tokens.SecurityToken jwt = new JwtSecurityToken();
-
- return tokendHandler.ValidateToken(accessToken, validationParameters, out jwt);
- }
Step 9
Chatbot will return message as 'Logged in as ' appending name from identity object of claim principal. Keep trying different services of Azure with this authentication. Happy Chatting! :)
Read more on Microsoft Bot Framework,