Implementing authentication through Facebook

Enough of the theory and samples. Time for us to convert the requirements into reality. We will start with the authentication module first. Recall that based on our discussion in the last chapter, this module needs to support a secure login, a forgotten password, as well as register user functionality. Once we are done with it, we will have completed a substantial part of our app, as per the following flowchart:

We are going to leverage Facebook authentication to implement this module of our system. We could have used any of the other providers as well, such as Twitter, Google, Microsoft, and so on, but since it's a fun chat app, Facebook is most appropriate. In Chapter 7, To the Cloud, we will see how we can support multiple authentication providers and let the user decide which provider they wish to use for authentication. To code this module, we will perform the following steps:

  1.  To integrate Facebook with our app, we first need to create a Facebook App ID. So, go to https://developers.facebook.com/apps/ and click on Add a New App button. Please note, this requires us to sign in to our Facebook account, so you need to have a Facebook account to do this activity. If you don't have a Facebook account, please create one. On clicking the button, the following screen will display:
  1. Enter Display Name and Contact Email, and click on the Create App ID button. It will display a Completely Automated Public Turing Test to tell Computers and Humans Apart (CAPTCHA) verification page. On successful verification, the App ID will get created and a products page will be displayed, as shown in the following screenshot:
  1. We need to choose Facebook Login, so click on the Setup button, which will display when we hover over Facebook Login. On clicking Setup, Facebook will display a select platform page, as shown in the following screenshot:
  1. Choose Web and then enter the website URL in the next screen. This is the URL of your web app. Once we run our app using Visual Studio or dotnet run, we can see the URL (or we will directly see launchSettings.json or project properties). Copy the URL and paste it in the Site URL field, as shown in the following screenshot:
  1. Click the Save button. Now, click on the Facebook Login on the left navigation panel in the PRODUCTS section and make the following selections:

OAuth redirect URIs should be correct; otherwise, the flow will not work. There is no point in putting the lock on the door and keeping the key alongside, similarly, there is no point in using authentication and using an HTTP protocol for OAuth. The URI should be using an HTTPS protocol. This is a demo app, so I have used the HTTP protocol. However, for any non-demo app, we should always use HTTPS. We can provide multiple URLs in the OAuth redirect URIs, so once we publish the app to Azure, we would need to add one more URL here. With this, our Facebook app is set up. We need an App ID and App Secret, which we will copy by navigating to the Dashboard of the app we just created. This can be done by clicking on the Dashboard in the left-hand side navigation. Copy the App ID and App Secret in a notepad for our use:

With this, we are done with the app setup in Facebook. We will come back to the portal again when we make our app public and publish it to Azure. For now, we will start coding. As discussed earlier, we will make use of middleware to perform authentication. The pattern to use middleware is also very simple. First, add the middleware in the ConfigureServices method and then use it in the Configure method of Startup.cs. The framework takes care of most of the heavy lifting, so the coding part is quite simple as well, as we will see.

  1. We will use Facebook and cookie authentication. To use them, we add the following using directives in Startup.cs:
using Microsoft.AspNetCore.Authentication.Facebook;
using Microsoft.AspNetCore.Authentication.Cookies;
  1. We need to use the App ID and App Secret to integrate Facebook authentication with our web app. To do so, we need to read the values of the App ID and App Secret. This can be done through reading from the appsettings.json or from the User Secret Manager tool in development. Essentially, these secrets should be kept way from the application code and real apps deployed on Azure should be fetched from the Azure Key-vault. We will use the User Secret Manager tool as well as Key-vault in Chapter 7, To the Cloud. For the sake of simplicity, in this demo, we will make use of asppsettings.json to read these values (though its not recommended for actual systems), as shown here:
 "FacebookAuthenticationAppId": "148203862468266",
"FacebookAuthenticationAppSecret": "<<App Secret>>" //// Your App
Secret goes here.
  1. In the ConfigureServices method of Startup.cs, we will write the following lines of code to add the authentication middleware and read the App ID and App Secret from configuration, by using the following code:
//// Configure Authentication, we will challenge the user, via Facebook and sign in via Cookie ////authentication, so setting the appropriate values.
services.AddAuthentication(options =>
{
options.DefaultChallengeScheme =
FacebookDefaults.AuthenticationScheme;
options.DefaultSignInScheme =
CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme =
CookieAuthenticationDefaults.AuthenticationScheme;
}).AddFacebook(options =>
{
options.AppId =
this.Configuration["FacebookAuthenticationAppId"]; //// AppId
options.AppSecret =
this.Configuration["FacebookAuthenticationAppSecret"]; // App
Secret
}).AddCookie();

The comments make it very clear that we would challenge the user through Facebook and sign in using cookie authentication. We have configured Facebook to use the App ID and App Secret from the appsettings and also added cookie authentication. Also, to keep the application secure, it's highly recommended that we always enforce SSL; that is, use HTTPS. We can do so with the following line of code, while adding MVC:

 //// Since HTTPS is secure, lets make it mandatory, by using the RequireHttpsAttribute Filter
services.AddMvc(options =>
{
options.Filters.Add(new RequireHttpsAttribute());
});
  1. In the Configure method of Startup.cs, write the following code between app.UseStaticFiles and app.UseMvc:
 app.UseAuthentication();

This ordering is important and will ensure that static resources, such as js, css, and image files, will not have to go through authentication. They would be served without authentication, while before any other page access request or authentication can kick in. If we run the app now, the authentication would still not kick in. We need to decorate the controller/controller action(s) with the [Authorize] attribute, which we only want the authenticated user to access. We can also configure this at the global level. We saw the sample for this in the last chapter.

  1. We will decorate the Index action of HomeController with the [Authorize] attribute, so that we can challenge the user if he/she is not logged in, as shown here:
 public class HomeController : Controller
{
[Authorize]
public IActionResult Index()
{
return this.View();
}
}

Now, if we run the app and we have followed all the steps correctly so far, we will see the Facebook login page, as shown here:

It will also ask for permission to use the user profile, as shown here:

Click on Continue as <your name> and we will be navigated to the Index page.

It's not a scaleable model to put [Authorize] on every controller as it is susceptible to mistakes. New developers adding a new controller may forget to do so and it may not be caught unless someone browses the URL for that controller, so it is recommended that we configure authentication in the ConfigureServices method of Startup.cs, as shown here:

services.AddMvc(config =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});

After applying this policy, wherever we need non-authenticated access, we can insert the [AllowAnonymous] attribute. This is safer from a security perspective.

Now, we have a working login, with the option to register a user as well the provision to reset the password, if the user forgot it, without doing any custom coding. We can safely rely on Facebook to take care of this stuff. We will wrap up this module by seeing how we can do an explicit sign in and sign out, so that if we wish to sign out or sign in explicitly by clicking on a link or button, we can handle it. To do so, we will add a new controller called AuthenticationController with two actions, SignIn and SignOut, as shown here:

 [Route("authentication")]
public class AuthenticationController : Controller
{
private readonly ILogger<AuthenticationController> logger;

public AuthenticationController(ILogger<AuthenticationController>
logger)
{
this.logger = logger;
}

[Route("signin")]
public IActionResult SignIn()
{
logger.LogInformation($"Calling {nameof(this.SignIn)}");
return Challenge(new AuthenticationProperties { RedirectUri =
"/" });
}

[Route("signout")]
[HttpPost]
public async Task<IActionResult> SignOut()
{
await
HttpContext.SignOutAsync(CookieAuthenticationDefaults
.AuthenticationScheme);
return RedirectToAction("Index", "Home");
}
}

The code is simple but an explanation is important, as we have used a lot of things here:

  • We have used attribute routing at the controller level, by using the [Route("authentication")] attribute. This may seem unnecessary as its name is the same as that of the controller, but the intent is to demonstrate how the Route attribute is used at a controller level. Had we used [Route("auth")], all requests to /auth/ would redirect to this controller.
  • Next, we see that the AuthenticationController derives from the Controller class. This is mandatory when we create any controller; it should derive from the Controller class. We can have it derive from some other class, say BaseController, but then BaseController should derive from the Controller class.
  • Next, we see we have a field of type ILogger<AuthenticationController> called logger, which would be used to do the logging with the AuthenticationController as the category.
  • Then, we have the constructor for the AuthenticationController. This takes in a dependency of ILogger<AuthenticationController>, which is injected by default by the framework. This demonstrates DI.
  • There is an action created with the name SignIn. Note that it also uses attribute routing, as it's decorated with [Route("signin")]. The return type of this action is IActionResult. As is evident, the first line of action is for logging the information. The important thing in this action is that its returns a ChallengeResult, taking in the AuthenticationProperties as the parameter. It's important to set the RedirectUri as it is responsible for redirecting the page to the appropriate page after authentication. ChallengeResult would use the configured challenge scheme, Facebook, in this case.
  • The last action is SignOut. Again, it uses attributebased routing. It would be served when a POST request comes as it is decorated with the [HttpPost] attribute. We also note the async keyword to demonstrate that we can have async controller actions. It uses the SignOutAsync method of HttpContext and signs out using the cookie authentication scheme. Post sign out, it redirects the user to the Index action of the Home controller. If the Index action of the Home controller has the [Authorize] attribute applied, it may take the user to the login screen and if cookies are not cleared from the browser, you may have the user logged back in.

In the View, we will do the following. If the user is not signed in, show him the Sign In button, or show him the Sign Out button. This can be done easily in the _Layout.cshtml by the writing the following code:

  @if (User.Identity.IsAuthenticated) /// If user is authenticated
{
<li>
<br/>
<form method="post" asp-controller="Authentication" asp-
action="SignOut">
<button type="submit" class="btn btn-primary">Sign
Out</button>
</form>
</li>
}
else
{
<li><a asp-area="" class="btn btn-primary" asp-
controller="Authentication" asp-action="SignIn">Sign In</a></li>
}

By doing this in _Layout.cshtml, we ensure this functionality is common across all the pages. The @prefix on Razor tells the RazorEngine that it is C# code. We first check if the user is authenticated; if yes, User.Identity.IsAuthenticated would be true. Inside this condition, we display the Sign Out button inside a form tag. The form tag is important, as we are doing a submit action on the Sign Out button, so on clicking it, the form would be posted to the server. This call should never ever be HttpGet. The form tag uses the post method and uses tag helpers to specify the controller and action as Authentication and SignOut, respectively.

HTTP method definitions (https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html) recommend the usage of HttpPost methods over HttpGet methods for security reasons (https://www.w3.org/Protocols/rfc2616/rfc2616-sec15.html#sec15.1.3), when the data is being submitted to the server, as per the following excerpt:
If your of services use the HTTP protocol, then you SHOULD NOT use GET-based forms for the submission of sensitive data. Otherwise, this will cause the data to be encoded in the Request-URI. The request URI will be logged somewhere by existing servers, proxies, and user agents, and it might be accessible to third parties. Servers can use POST-based form submission instead.

This completes our authentication module, and we are ready to develop the SignalR Chat hub module.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset