title: Azure Bot project with client and server in same web app
body: <p>I've made an Azure bot application using the BotFramework v4 and used the <a href="https://github.com/Microsoft/BotFramework-WebChat" rel="nofollow noreferrer">WebChat control</a> as an interface. I noticed that the bot server's dotnetcore app had a wwwroot folder with a placeholder HTML page in it, so thought it might be expedient to host the webchat client there. But now seems counter-intuitive that my webchat client is using DirectLine to send activities back to the same back-end that served it.</p>
  
  <p>I had chosen the webchat client because I need to customise the appearance of the client. I also need the MVC app that serves the bot client to include Azure Active Directory B2C authentication (which it does). Users should see the webchat client before and after authentication but the bot back-end (handling the activities) needs to know whether the user is logged in and modify its behaviour accordingly (and I am struggling to achieve that part with DirectLine).</p>
  
  <p>So my first question (ever on StackOverflow) is: With the Bot back-end and the webchat client front-end being hosted in the same, single Azure web app, is it necessary to use DirectLine, or is there a simpler way of doing this?</p>
  
  <p>Relevant code in my Startup.cs:</p>
  
  <pre><code>public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
  {
      _loggerFactory = loggerFactory;
  
      app.UseStaticFiles(); // to allow serving up the JS, CSS, etc., files.
      app.UseBotFramework(); // to add middleware to route webchat activity to the bot back-end code
      app.UseSession(); // to enable session state
      app.UseAuthentication(); // to enable authentication (in this case AAD B2C)
      app.UseMvcWithDefaultRoute(); // to add MVC middleware with default route
  }
  </code></pre>
  
  <p>Also in Startup.cs:</p>
  
  <pre><code>    public void ConfigureServices(IServiceCollection services)
      {
  </code></pre>
  
  <p>// standard code to add HttpContextAssessor, BotServices, BotConfigs and memory storage singletons ommitted for brevity ... </p>
  
  <pre><code>        services.AddAuthentication(sharedOptions =&gt;
          {
              sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
              sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
          })
          .AddAzureAdB2C(options =&gt; Configuration.Bind("Authentication:AzureAdB2C", options))
          .AddCookie();
  
          services.AddMvc();
  
          services.AddSession(options =&gt;
          {
              options.IdleTimeout = TimeSpan.FromHours(1);
              options.CookieHttpOnly = true;
          });
  
  
          // Create and add conversation state.
          var conversationState = new ConversationState(dataStore);
          services.AddSingleton(conversationState);
  
          var userState = new UserState(dataStore);
          services.AddSingleton(userState);
  
          services.AddBot&lt;MyBot&gt;(options =&gt;
          {
              options.CredentialProvider = new SimpleCredentialProvider(endpointService.AppId, endpointService.AppPassword);
              options.ChannelProvider = new ConfigurationChannelProvider(Configuration);
  
              // Catches any errors that occur during a conversation turn and logs them to currently
              // configured ILogger.
              ILogger logger = _loggerFactory.CreateLogger&lt;RucheBot&gt;();
              options.OnTurnError = async (context, exception) =&gt;
              {
                  logger.LogError($"Exception caught : {exception}");
                  await context.SendActivityAsync("Sorry, it looks like something went wrong.");
              };
          });
  
      }
  </code></pre>
  
  <p>My controller's Index method:</p>
  
  <pre><code>    public async Task&lt;ActionResult&gt; Index()
      {
          string userId;
          if (User.Identity.IsAuthenticated)
          {
              string aadb2cUserId = User.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier").Value;
              Users.EnsureAccountExists(aadb2cUserId); // ensure account with given AAD identifier is know locally (by creating it if not)
              userId = $"ia_{aadb2cUserId}";
          }
          else
          {
              userId = $"na_{Guid.NewGuid()}";
          }
  
  
          HttpClient client = new HttpClient();
          string directLineUrl = $"https://directline.botframework.com/v3/directline/tokens/generate";
          HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, directLineUrl);
  
          // TODO: put this in the config somewhere
          var secret = "&lt;the secret code from my bot's DirectLine channel config in the Azure portal&gt;";
          request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", secret);
          string jsonUser = JsonConvert.SerializeObject(new { User = new { Id = userId } });
          request.Content = new StringContent(jsonUser, Encoding.UTF8, "application/json");
          var response = await client.SendAsync(request);
  
          string token = string.Empty;
  
          if (response.IsSuccessStatusCode)
          {
              var body = await response.Content.ReadAsStringAsync();
              token = JsonConvert.DeserializeObject&lt;DirectLineToken&gt;(body).token;
          }
  
          var config = new ChatConfig()
          {
              Token = token,
              UserId = userId,
          };
  
          return View(config);
      }
  </code></pre>
  
  <p>And finally the code in the associated view:</p>
  
  <pre><code>&lt;div id="webchat"&gt;&lt;/div&gt;
  &lt;script type="text/javascript"&gt;
  ...
          /// Called asynchronously during the page load
          function renderWebChat( withSound )
          {
              var webchatOptions =
              {
                  directLine: window.WebChat.createDirectLine( { secret: '@Model.Token'} ),
                  userID: '@Model.UserId'
              };
  
              if ( withSound )
              {
                  webchatOptions.webSpeechPonyfillFactory = window.WebChat.createBrowserWebSpeechPonyfillFactory();
              }
  
              window.WebChat.renderWebChat( webchatOptions, document.getElementById( 'webchat' ) );
  
              document.querySelector( '#webchat &gt; *' ).focus();
          }
  
  &lt;/script&gt;
  </code></pre>