In this chapter, you will learn about ASP.NET Identity and how you can use it to implement registration and login in your application. You will add the authentication from scratch to learn how all the pieces fit together.
ASP.NET Identity is a framework that you need to install either with the NuGet Manager or by adding it manually in the .csproj file. It can handle several types of authentication, but in this chapter, you will focus on Forms Authentication.
The first thing you need to add is a User entity class that inherits from an identity base class, which gives you access to properties such as Username, PasswordHash, and Email. You can add as many properties to the User class as your application needs, but in this chapter, you will only use some of the inherited properties.
The User class needs to be plugged into a class called UserStore, provided by the Identity framework. It is used when creating and validating a user that then is sent to a database; Entity Framework is supported out of the box. You can implement your own UserStore, for a different database provider.
The User class needs to be plugged into an IdentityDb class that handles all communication with an Entity Framework-supported database, through an Entity Framework DbContext. The way this is done is by making your existing VideoDbContext inherit from the IdentityDbContext class instead of the current DbContext class.
The UserStore and the IdentityDbContext work together to store user information and validate against the hashed passwords in the database.
Another class involved in the process is the SignInManager, which will sign in a user once the password has been validated. It can also be used to sign out already logged in users. A cookie is used to handle Forms Authentication sign-in and sign-out. The cookie is then sent with every subsequent request from the browser, so that the user can be identified.
The last piece is the Identity Middleware that needs to be configured to read the cookie and verify the user.
The [Authorize] attribute can be applied to a controller to restrict user access; a user must be signed in and verified to have access to the actions in that controller.
The [AllowAnonymous] attribute can be applied to actions to allow any visitor access to that action, even if they aren’t registered or signed in.
You can use parameters with the [Authorize] attribute to restrict the access even beyond being logged in, which is its default behavior. You can, for instance, add the Roles parameter to specify one or more roles that the user must be in to gain access.
You can also place the [Authorize] attribute on specific actions, instead of on the controller class, to restrict access to specific actions.
Let’s start by adding the [Authorize] attribute to the HomeController class, to grant access only to logged in users. Let’s also add the [AllowAnonymous] attribute to the Index action, so that any visitor can see the video list.
[Authorize]
public class HomeController : Controller
{
...
}
[AllowAnonymous]
public ViewResult Index()
{
...
}
Once you have changed the inheritance on the VideoDbContext from the current DbContext to the IdentityDbContext, the Identity services can be configured in the ConfigureServices method, and in the Identity middleware installed in the Configure method, in the Startup class.
The services that need to be configured are the UserStore and SignInManager.
public class VideoUser : IdentityUser { }
public class VideoDbContext : IdentityDbContext<VideoUser>
{
...
}
using AspNetCore22Intro.Entities;
using Microsoft.AspNetCore.Identity;
services.AddIdentity<VideoUser, IdentityRole>();
services.AddIdentity<VideoUser, IdentityRole>()
.AddEntityFrameworkStores<VideoDbContext>();
app.UseAuthentication();
The complete VideoUser class:
public class VideoUser : IdentityUser
{
}
Now that the configuration is out of the way, it is time to create a new migration that adds the necessary AspNet identity tables to the database.
Now that all the configuration and database table creation is done, it is time to focus on how a user can register with the site.
If you run the application as it stands right now, the Identity middleware will redirect to the /Account/Login URL, which doesn’t exist yet. Instead, the next piece of middleware handles the request, and the message Hello from configuration will be displayed in the browser.
To display a Login view, you must add an AccountController class with a Login action. And to log in, the user needs to register. You therefore must implement a Register view, and a Register action in the AccountController class.
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthentication();
public class AccountController : Controller
{
}
[HttpGet]
public IActionResult Register()
{
return View();
}
[Required, MaxLength(255)]
public string Username { get; set; }
[Required, DataType(DataType.Password)]
public string Password { get; set; }
[DataType(DataType.Password), Compare(
nameof(Password))]
public string ConfirmPassword { get; set; }
@model RegisterViewModel
@{ ViewBag.Title = "Register"; }
<form method="post" asp-controller="Account" asp-action="Register"></form>
<div asp-validation-summary="ModelOnly"></div>
<div>
<label asp-for="Username"></label>
<input asp-for="Username" />
<span asp-validation-for="Username"></span>
</div>
<div>
<input type="submit" value="Register" />
</div>
using AspNetCore22Intro.ViewModels;
[HttpPost]
public async Task<IActionResult> Register(RegisterViewModel model)
{
}
if (!ModelState.IsValid) return View();
var user = new VideoUser { UserName = model.Username };
private UserManager<VideoUser> _userManager;
private SignInManager<VideoUser> _signInManager;
public AccountController(UserManager<VideoUser> userManager, SignInManager<VideoUser> signInManager)
{
_userManager = userManager;
_signInManager = signInManager;
}
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user, false);
return RedirectToAction("Index", "Home");
}
else
{
foreach (var error in result.Errors)
ModelState.AddModelError("", error.Description);
}
return View();
The complete code for the RegisterViewModel class:
public class RegisterViewModel
{
[Required, MaxLength(255)]
public string Username { get; set; }
[Required, DataType(DataType.Password)]
public string Password { get; set; }
[DataType(DataType.Password), Compare(nameof(Password))]
public string ConfirmPassword { get; set; }
}
The complete code for the AccountController class:
public class AccountController : Controller
{
private readonly UserManager<VideoUser> _userManager;
private readonly SignInManager<VideoUser> _signInManager;
public AccountController(UserManager<VideoUser> userManager, SignInManager<VideoUser> signInManager)
{
_userManager = userManager;
_signInManager = signInManager;
}
[HttpGet]
public IActionResult Register()
{
return View();
}
[HttpPost]
public async Task<IActionResult> Register(RegisterViewModel model)
{
if (!ModelState.IsValid) return View();
var user = new VideoUser { UserName = model.Username };
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user, false);
return RedirectToAction("Index", "Home");
}
else
{
foreach (var error in result.Errors)
ModelState.AddModelError("", error.Description);
}
return View();
}
}
The complete code for the Register view:
@model RegisterViewModel
@{ ViewBag.Title = "Register"; }
<h1>Register</h1>
<form method="post" asp-controller="Account" asp-action="Register">
<div asp-validation-summary="ModelOnly"></div>
<div>
<label asp-for="Username"></label>
<input asp-for="Username" />
<span asp-validation-for="Username"></span>
</div>
<div>
<label asp-for="Password"></label>
<input asp-for="Password" />
<span asp-validation-for="Password"></span>
</div>
<div>
<label asp-for="ConfirmPassword"></label>
<input asp-for="ConfirmPassword" />
<span asp-validation-for="ConfirmPassword"></span>
</div>
<div>
<input type="submit" value="Register" />
</div>
</form>
In this section, you will implement login and logout in your application. The links will be added to a partial view called _LoginLinks that you will add to the Views/Shared folder. The partial view will then be rendered from the _Layout view using the @Partial or @PartialAsync method.
When an anonymous user arrives at the site, Login and Register links should be available. When a user has logged in or registered, the username and a Logout link should be visible.
You must also create a new view called Login in the Views/Account folder, a view that the Login link opens by calling a Login action in the Account controller.
To work with users and sign-in information in views, you inject the SignInManager and UserManager, similar to the way you use dependency injection in methods and constructors in classes.
When an anonymous user clicks a restricted link, like the Edit link, a ReturnUrl parameter is sent with the URL, so that the user will end up on that view when a successful login has been made. When creating the LoginViewModel you must add a property for the return URL, so that the application can redirect to it. Below is an example URL with the ReturnUrl parameter.
https://localhost:44341/Account/Login?ReturnUrl=%2FHome%2FEdit%2F1
This partial view will contain the Login and Register links that will be visible when an anonymous user visits the site, and a Logout link and the username when the user is logged in.
@using Microsoft.AspNetCore.Identity
@inject SignInManager<VideoUser> SignInManager
@inject UserManager<VideoUser> UserManager
@if (SignInManager.IsSignedIn(User))
{
// Signed in user
}
else
{
// Anonymous user
}
<div>@User.Identity.Name</div>
<form method="post" asp-controller="Account" asp-action="Logout">
<input type="submit" value="Logout" />
</form>
<a asp-controller="Account" asp-action="Login">Login</a>
<a asp-controller="Account" asp-action="Register">Register</a>
<div>
<partial name="_LoginLinks"/>
</div>
The complete code for the _LoginLinks partial view:
@using Microsoft.AspNetCore.Identity
@inject SignInManager<VideoUser> SignInManager
@inject UserManager<VideoUser> UserManager
@if (SignInManager.IsSignedIn(User))
{
// Signed in user
<div>@User.Identity.Name</div>
<form method="post" asp-controller="Account" asp-action="Logout">
<input type="submit" value="Logout" />
</form>
}
else
{
// Anonymous user
<a asp-controller="Account" asp-action="Login">Login</a>
<a asp-controller="Account" asp-action="Register">Register</a>
}
The complete code for the _Layout view:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>@ViewBag.Title</title>
</head>
<body>
<div>
<div>
<partial name="_LoginLinks"/>
</div>
@RenderBody()
</div>
<footer>
@RenderSection("footer", false)
@await Component.InvokeAsync("Message")
</footer>
</body>
</html>
The SignOutAsync method on the SignInManager must be called to log out a user when the Logout button is clicked. The Logout action in the Account controller must be asynchronous because the SignOutAsync method is asynchronous.
[HttpPost]
public async Task<IActionResult> Logout() { }
await _signInManager.SignOutAsync();
return RedirectToAction("Index", "Home");
The complete code for the Logout action:
[HttpPost]
public async Task<IActionResult> Logout()
{
await _signInManager.SignOutAsync();
return RedirectToAction("Index", "Home");
}
This model is responsible for passing the login information provided by the user, and the ReturnUrl URL parameter value, to the HTTP POST Login action.
The model needs four properties: Username, Password, RememberMe, and ReturnUrl. The RememberMe property determines if the cookie should be a session cookie or if a more persistent cookie should be used.
using System.ComponentModel.DataAnnotations;
[Required]
[DataType(DataType.Password), Required]
[Display(Name = "Remember Me")]
The complete code for the LoginViewModel class:
public class LoginViewModel
{
[Required]
public string Username { get; set; }
[DataType(DataType.Password), Required]
public string Password { get; set; }
public string ReturnUrl { get; set; }
[Display(Name = "Remember Me")]
public bool RememberMe { get; set; }
}
This action will be called when the user clicks the Login link. You will need to create an instance of the LoginViewModel and assign the return URL, passed into the action, to its ReturnUrl property. Then pass the model to the view.
[HttpGet]
public IActionResult Login(string returnUrl ="")
{
}
var model = new LoginViewModel { ReturnUrl = returnUrl };
return View(model);
The complete code for the HTTP GET Login action:
[HttpGet]
public IActionResult Login(string returnUrl ="")
{
var model = new LoginViewModel { ReturnUrl = returnUrl };
return View(model);
}
The HTTP POST Login action will be called when the user clicks the Login button in the Login view. The view’s login form will send the user data to this action; it therefore must have a LoginViewModel as a parameter. The action must be asynchronous because the PasswordSignInAsync method provided by the SignInManager is asynchronous.
[HttpPost]
public async Task<IActionResult> Login(LoginViewModel model)
{
}
if (!ModelState.IsValid) return View(model);
var result = await _signInManager.PasswordSignInAsync(model.Username, model.Password, model.RememberMe, false);
if (result.Succeeded)
{
}
if (!string.IsNullOrEmpty(model.ReturnUrl) && Url.IsLocalUrl(model.ReturnUrl))
{
}
else
{
}
return Redirect(model.ReturnUrl);
return RedirectToAction("Index", "Home");
ModelState.AddModelError("", "Login failed");
return View(model);
The complete code for the HTTP POST Login action:
[HttpPost]
public async Task<IActionResult> Login(LoginViewModel model)
{
if (!ModelState.IsValid) return View();
var result = await _signInManager.PasswordSignInAsync(model.Username, model.Password, model.RememberMe, false);
if (result.Succeeded)
{
if (!string.IsNullOrEmpty(model.ReturnUrl) && Url.IsLocalUrl(model.ReturnUrl))
{
return Redirect(model.ReturnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
ModelState.AddModelError("", "Login failed");
return View(model);
}
You need to add a view called Login to the Account folder, to enable visitors to log in.
@model LoginViewModel
@{ ViewBag.Title = "Login"; }
<form method="post" asp-controller="Account" asp-action="Login" asp-route-returnurl="@Model.ReturnUrl"></form>
<div asp-validation-summary="ModelOnly"></div>
<div>
<label asp-for="Username"></label>
<input asp-for="Username" />
<span asp-validation-for="Username"></span>
</div>
<div>
<input type="submit" value="Login" />
</div>
The complete markup for the Login view:
@model LoginViewModel
@{
ViewBag.Title = "Login";
}
<h2>Login</h2>
<form method="post" asp-controller="Account" asp-action="Login" asp-route-returnurl="@Model.ReturnUrl">
<div asp-validation-summary="ModelOnly"></div>
<div>
<label asp-for="Username"></label>
<input asp-for="Username" />
<span asp-validation-for="Username"></span>
</div>
<div>
<label asp-for="Password"></label>
<input asp-for="Password" />
<span asp-validation-for="Password"></span>
</div>
<div>
<label asp-for="RememberMe"></label>
<input asp-for="RememberMe" />
<span asp-validation-for="RememberMe"></span>
</div>
<div>
<input type="submit" value="Login" />
</div>
</form>
In this chapter, you used ASP.NET Identity to secure your application, implementing registration and login from scratch.
The first thing you did was to add a VideoUser entity class that inherited the IdentityUser base class. This gave you access to properties such as Username, PasswordHash, and Email.
Then you plugged the VideoUser entity into a UserStore and an IdentityDb class. This made it possible to create and validate a user, which then was stored in the database.
Then you added middlware to handle HTTPS redirection and user authentication.
The UserManager and SignInManager were then used to implement registration and login for users, with a cookie that handles the Forms Authentication.
The [Authorize] and [AllowAnonymous] attributes were used to restrict user access to controller actions.
You also added views to register, log in, and log out a user.
In the next chapter, you will use front-end frameworks to style the application.
Join our mailing list to receive the latest news and updates from our team.
Don't worry, your information will not be shared.
50% Complete
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.