Refer to the post about User management for the setup of AspNet Identity the controller that can be used for creating, deleting and updating (editing) Users here.
Notice that I show Authentication, Usermanagement and Authorization on a standard MVC controller first. The Web Api counterparts are done in future posts.

In this post we will cover how to authenticate the given User accounts, so we make sure only the allowed users are let into our application.

What this post will cover

  • Setting up Authentication

What this post will not cover

  • Setting up Users
  • Role based Authorization

Authentication

For AspNet Identity the most fundamental activity is the authentication via the Authorize Attribute. If you apply this to an action method it tells MVC that the requesting User needs to be authenticated before accessing this method.

But you cannot simply put the attribute on an action method, because if a User is not authenticated it performs a Redirect to the /Account/Login URL. And because we use default routing and have no AccountController with a method Login set up yet, it would yield a 404 Not Found response. The URL for the redirect (/Account/Login) can be customized in the Startup.cs like so:

services.ConfigureApplicationCookie(cookie => cookie.LoginPath = "/Users/Login");

So to use authentication we need to set up an AccountController and the Login method. But first we need a Model for our LoginData which we put in the Models namespace:

public class LoginData
{
[Required]
public string Email{get; set;}
[Required]
public string Password{get; set;}
}

We again see the Required Attribute from model binding, so that those properties are obligatory.

To create a way to login we add an AccountController like the following snippet. Note that we use the Authorize Attribute on the Controller level. This makes it necessary to apply the AllowAnonymous Attribute to methods like Login, which obviously need to be accessed before one can be Authenticated.

namespace MyWebApp.Controllers 
{
[Authorize]
public class AccountController : Controller 
{
private UserManager<MyUser> _userManager;
private SignInManager<MyUser> _signInManager;
public AccountController(UserManager<MyUser> userManager,
SignInManager<MyUser> signInManager)
{
_userManager = userManager;
_signInManager = signInManager;
}
[AllowAnonymous]
public IActionResult Login(string returnUrl) 
{
ViewBag.returnUrl = returnUrl;
return View();
}
[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> Login(LoginData mylogin, string returnUrl) 
{
if (ModelState.IsValid) 
{ 
MyUser user = await _userManager.FindByEmailAsync(mylogin);
if (user != null) 
{
await signInManager.SignOutAsync();
SignInResult result = await signInManager.PasswordSignInAsync(user, mylogin.Password, false, false);
if (result.Succeeded) 
return Redirect(returnUrl ?? "/"); 
}
ModelState.AddModelError(nameof(LoginModel.Email), "Invalid user or password");
}
return View(mylogin);
}
} 
}

So as you can tell there is a lot of going on in these methods.

First of all we can see that both methods retrieve a string parameter named returnUrl. This parameter comes from the query string which is added on redirect and looks like this:

/Account/Login?ReturnUrl=%2FHome%2FIndex

In this case the User was redirected from the restricted action method /Home/Index. (that we simply assumed for this example)
This ReturnUrl can be used to redirect back to the action the user came from to create a seamless process.

On Redirect, the Login method will display a ViewResult that renders some Login.cshtml from the Views namespace of our application.
This will likely have a form on it, that posts to the async Login method.

The Login method withe the HttpPost attribute method gets the LoginData object on a Post performed on the form. This LoginData is used to locate the User. We locate the user by email, like most web apps do.

In the Post action method the model state is validated, then we use the UserManager<T> instance that is injected to the AccountController to find our user by email. If no user is found (user == null) we return the view with login data and add the error messages to the ModelState.

If a user is found we use the SingInManager for two steps:

  1. The SignoutAsync ends every current sessions with this user
  2. PasswordSignInAsync performs the actual Authentication

The arguments to the PasswordSignInAsync are the User object, the login DTOs password property, and a boolean argument if the cookie is persistent as well as if the user should be locked out if the password is correct.

The PasswordSignInAsync returns a SignInResult. This is validated similar to the IdentityResult we saw in the AdminController of UserManagement Post . If the SignInResult.Succeeded returns true we call Redirect on the returnUrl, if present or else on the root route (“/”)

If it was not successful we enrich the ModelState with errors to display.

The completed Authentication adds a cookie to the response which is used to identify the users session and the account. Cookie handling is done by the middleware (if you did not tamper with it 😉 ).

This process demonstrated is called single factor authentication. There is also a so called two factor authentication. In that case you have something sent to your mobile device or such, during the authentication process. Other processes use sms, email, QR codes etc. Maybe I will look at that in future posts.

What does the Authorize Attribute do

The Authorize Attribute works internally in the middleware with the HttpContext.User property. This returns an IPrincipal object, which is defined in System.Security.Principal and has the following properties:

Identity Identity interface object, describes user from request
IsInRole(role) true if user is a member of the specified role, or false otherwise

IIdentity AuthenticationType (string with mechanism to authenticate the user)
IsAuthenticated
Name of the User

With AspNetCore 2.0 the Identity middleware by default uses cookies sent by the browser to determine whether the user has been authenticated. If this is the case the IsAuthenticated property of the Identity returns.

Before with AspNetCore 1.0 you could register the middleware used for this by yourself which lead to some irritating and offcially unsupported behaviors.

Now that we have a way to authenticate in place, in the next post we will look at how the Authorization is going to work and what we need to implement for it to work.

Summary

In this post we setup the necessary steps to authenticate Users with a login. For this we built on the UserManagement system we created in the blog post before this one.

First of all we created a LoginData DTO and an AccountController that could be redirected to on the need for authentication.
With this in place we looked how this process works and what the Authorize attribute does internally.


0 Comments

Leave a Reply

Avatar placeholder