2. Identity Framework

Identity Framework introduction

ASP.NET Core Identity framework is basically a membership system that’s baked into ASP.NET Core. Using ASP.NET Identity, we will be able to authenticate and authorize users. Authentication has to do with knowing who is trying to log into the site. Authorization will allow us to manage which permissions a certain user has to have to access a certain resource. So checking if the user can see a certain picture example. Furthermore, it can help us to manage roles, claims, and other more advanced options related to identity management in our ASP.NET Core applications. Now to do all this, it can even allow us to work with external providers such as Google or Facebook. To manage the authentication and authorization information, ASP.NET Identity can work with SQL Server out-of-the-box.

Configuring identity framework in our application

The first thing that we’ll do is make our context inherit from IdentityDbContext. This will make sure that we will get the correct tables in the database when we perform a migration.

using Data.Model;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Text;
namespace Data.Access
{
public class AppDbContext : IdentityDbContext<IdentityUser>
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
}
public DbSet<Project> Projects { get; set; }
}
}

Probably you need to add Microsoft.AspNetCore.Identity.EntityFrameworkCore as a reference via nugget PM in your data.access library project. 

We’ll then need to make some configuration changes in the application. So some work will be needed to be done in the Startup class of the application.

 public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<AppDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<AppDbContext>()
.AddDefaultTokenProviders();

Apart from the basic configuration to add support, ASP.NET Identity also allows us to further constrain the user registration of the site. If you, for example, want to force users to use a strong password, which we should, by the way, we can configure Identity so that it doesn’t allow simple passwords. We can also use options to configure how the security cookies should behave such as an expiration time and we can configure user options such as the fact that the email used upon registration has to be unique.

So in order for my application to support Identity, I have made some changes. I now inherit from IdentityDbContext, and I’m going to pass in the default type for the user, which is IdentityUser. So this change actually makes it so that my context now inherits from IdentityDbContext and the net result is that through my AppDbContext I will now be able to work with the types such as user we could add it by the IdentityDbContext. I don’t have to add all these manually.

I also use IdentityUser, which is the type that represents my default user.  If you need specific properties for the user you can actually inherit from IdentityUser. The extra properties you add will then also be stored automatically in their underlying database table. 

Let’s now create another migration. So let’s go back to the Package Manager console, and do a migration as we have done before. I’m going to create a new migration,

add-migration IdentityAdded

We changed our AppDbContext to inherit from IdentityDbContext and that brings along with it all these extra entities, and therefore, also corresponding tables.

Let’s now run an update database, and we now look at the corresponding database

update-database

Register user

To register the user, we adapt our account controller, and make use of the UserManager class (part of the identity framework)

[Route("api/[controller]")]
[ApiController]
public class AccountController : ControllerBase
{
UserManager<IdentityUser> _userManager;
public AccountController(UserManager<IdentityUser> userManager)
{
_userManager = userManager;
}
[HttpPost]
[Route("register")]
public IActionResult Register([FromBody]Login model)
{
var user = new IdentityUser { UserName = model.UserName, PasswordHash = model.Password };
var result = _userManager.CreateAsync(user, model.Password);
if (!result.Result.Succeeded)
{
return BadRequest(result.Result.Errors);
}

return Ok();
}

Check the Login model parameter of the Register method. I created a new class Login:

public class Login
{
public string UserName { get; set; }
public string Email { get; set; }
public string Password { get; set; }
}

You can test the Register method by using yarc:

Let us look at each line of code. First, We are creating a new applicationUser instance for the user input.

var user = new IdentityUser { UserName = model.UserName, PasswordHash = model.Password };

Next, we are using CreateAsync Method of the UserManager to create the user in the database.

var result = _userManager.CreateAsync(user, model.Password);


The above method returns result which is of type IdentityResult. It has two properties. One is Succeeded which is boolean value and the other is errors property, which is a collection of errors. If the user is registered successfully, we are then calling the method SignInAsync to sign in the user. 

_signInManager.SignInAsync(user, isPersistent: false);

Login

[HttpPost]
public async Task<IActionResult> Login([FromBody] Login login)
{
var user = await userManager.FindByNameAsync(login.UserName);
var result = await signInManager.PasswordSignInAsync(user, login.Password, true ,false);
if (!result.Succeeded)
{
}

return View();
}

Test your Login method with Yarc or Postman

We sign in the user with the PasswordSignInAsync method. This will create the Identity.Application cookie in our browser. Finally, we redirect the user to anywhere we want.

My form looks like:

@model Domain.Login
@{
ViewData["Title"] = "Login";
}
<h1>Login</h1>
<h4>Login</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Login" method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="UserName" class="control-label"></label>
<input asp-for="UserName" class="form-control" />
<span asp-validation-for="UserName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Email" class="control-label"></label>
<input asp-for="Email" class="form-control" />
<span asp-validation-for="Email" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Password" class="control-label"></label>
<input asp-for="Password" class="form-control" />
<span asp-validation-for="Password" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Login" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Logout

[HttpPost]
public async Task<IActionResult> Logout()
{
await signInManager.SignOutAsync();
return RedirectToAction("Index", "HomeController");
}

Authentication

Authentication is a process of confirming a user’s identity. It is a set of actions, we use to verify the user’s credentials against the ones in the database. For the user to be able to provide credentials, our application requires a Login page with the set of fields for our user to interact with.

We can unauthorize users to access an action by adding the [Authorize] attribute on top of that action:
e.g.:

      [Authorize]
public IActionResult Privacy()
{
return View();
}

If you navigate to the “Privacy” action, you get:

https://localhost:44330/Account/Login?ReturnUrl=%2FHome%2FPrivacy

We got this error because by default ASP.NET CORE tries to redirect an unauthorized user to the Login action of the account controller. You can see this because the URL has a query string ReturnUrl that provides the path to the action before the user was redirected to the login page.

If you log in successfully the Identity.Application cookie in our browser is set, and you will be able to navigate to the Privacy method.

Extra reading:

https://docs.microsoft.com/en-us/aspnet/core/security/authentication/identity?view=aspnetcore-3.1&tabs=visual-studio