Introduction to ASP.NET Core part 26: authorisation basics and logging in

Introduction

In the previous post we continued looking into the basics of user management in .NET Core. We first created a separate database for our users and related objects such as claims and tokens. We then went on and created the components necessary for user creation: the view, the account controller and the user registration view model. We used the built-in generic UserManager of T for the database section of user management. The UserManager object provides a wide range of functions to handle users: insert a new user, retrieve a user by ID, confirm an email and much more. Therefore UserManager is an out-of-the-box service class to connect the application user and the database.

In this post we’ll investigate the basics of what to do with our users: authorisation and logging in.

The Authorize attribute

One of the main goals of having users in a website is to impose restrictions on certain sections. There’s typically a public section which all anonymous users can view without logging in. Then there are those pages and functions that require the user to have an account and log in. A typical e-commerce site will allow anonymous users to peruse the product catalog but will require the user to log on in order to make a purchase. Furthermore the same e-commerce website will require a user to have special privileges to make modifications in the database like adding a new product or change the price.

The most basic gateway into access management in .NET Core is the Authorize attribute that’s used in controllers. It is located in the Microsoft.AspNetCore.Authorization namespace. It can be applied at the controller and action method level. E.g. if we take our Books controller we can add the attribute above the class declaration as follows:

[Authorize]
public class BooksController : Controller

Start the application now and navigate to the /books page. You should be redirected to the http://localhost:%5Bport_number%5D/Account/Login?ReturnUrl=%2Fbooks page. The URL tells us that the application wants us to log in on the /account/login resource. It doesn’t exist yet, we’ll create it soon.

We can apply the attribute to a single action method. Remove the attribute from the class declaration and put it above the GET Create function:

[HttpGet]
[Authorize]
public IActionResult Create()

You’ll now be able to view the books. Click on the link to add a new book. You should again be redirected to a login page: http://localhost:%5Bport_number%5D/Account/Login?ReturnUrl=%2Fbooks%2FCreate.

Note how the ReturnUrl query parameter stores where the user wanted to navigate before being redirected to the login page. It’s a common feature of websites to let the user continue to the requested resource after authentication. This query parameter serves as the basis for implementing this feature. Otherwise if we simply redirect the user to some default opening page then the user will need to navigate back to the page where they originally wanted to go. That gives a bad impression and your users will quickly get irritated.

The Authorize attribute can also accept a string parameter to describe which role the authenticated user should be in:

[Authorize(Roles = "Administrators")]

It’s possible to put an exception from the overall Authorize attribute using the AllowAnonymous attribute. If we put the Authorize attribute back to the controller…:

[Authorize]
public class BooksController : Controller

…then we can still make the /books/index resource publicly reachable:

[AllowAnonymous]
public IActionResult Index()

The other action methods in the controller will still require that the user be logged in.

Logging in

We’ll create a simple form with username, password and a remember me checkbox. We’ll store these in a view model along with the return URL property. Add the following in the Models folder:

using System.ComponentModel.DataAnnotations;

namespace DotNetCoreBookstore.Models
{
	public class UserLoginViewModel
    {
		[Required]
		public string Username { get; set; }
		[Required, DataType(DataType.Password)]
		public string Password { get; set; }
		[Display(Name ="Remember me")]
		public bool RememberMe { get; set; }
		public string ReturnUrl { get; set; }
	}
}

We’ll need a view in the /Views/Account folder called Login.cshtml:

@model DotNetCoreBookstore.Models.UserLoginViewModel

@{ 
    ViewBag.Title = "Login";
}

<h2>Login</h2>

<form method="post" asp-antiforgery="true">
    <div asp-validation-summary="All"></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" />
    </div>
    <input type="submit" value="Log in" />
</form>

Before we continue we need to add a new dependency to the account controller. We mentioned the SignInManager before and we’ll need it for logging in and out. We’ve already registered the necessary user management infrastructure in Startup.cs so we can just let MVC inject an instance of this object through the AccountController instructor:

public class AccountController : Controller
    {
		private readonly UserManager<User> _userManager;
		private readonly SignInManager<User> _signinManager;

		public AccountController(UserManager<User> userManager, SignInManager<User> signinManager)
		{
			if (userManager == null) throw new ArgumentNullException("User manager");
			if (signinManager == null) throw new ArgumentNullException("Sign in manager");
			_userManager = userManager;
			_signinManager = signinManager;
		}
//rest of code ignored
.
.
.
}

We’ll can finally add the GET and POST Login action methods to the AccountController. GET will display the login form and POST will perform the actual logging in.

[HttpGet]
public IActionResult Login()
{
	return View();
}

[HttpPost, ValidateAntiForgeryToken]
public async Task<IActionResult> Login(UserLoginViewModel userLoginViewModel)
{
	if (ModelState.IsValid)
	{
		var signInResult = await _signinManager.PasswordSignInAsync(userLoginViewModel.Username, userLoginViewModel.Password, userLoginViewModel.RememberMe, false);
		if (signInResult.Succeeded)
		{
			return Redirect(userLoginViewModel.ReturnUrl);
		}
	}
        ModelState.AddModelError("", "Login failure. Please check your username and password");
	return View(userLoginViewModel);
}

The GET Login method simply returns a view with no view model so that we display an empty form. POST Login must be decorated with the async keyword since we use an asynchronous method in its body. This method is PasswordSignInAsync which accepts 4 parameters: the username, the password, whether the authentication cookie should be persistent and whether the user should be locked out in case the login failed. If the login succeeded then we redirect the user to the return URL. The ReturnUrl property is populated by MVC using the ReturnUrl query parameter we saw above. So MVC is clever to combine the inputs from the sign in form and the query parameters and build a “unified” UserLoginViewModel object.

PasswordSignInAsync returns a SignInResult object which has a Succeeded property. You can probably guess what its purpose is. This object doesn’t explain why the login failed exactly since we don’t want to give away too much to a potential attacker. If the login process failed then we add a failure to the model state.

We can test the application now. Navigate to /books and click on either the “add new” or one of the “details” links. Both should lead to our login form. Provide the username and password you created in the previous post. You should then be redirected to the requested page. The authentication cookie has been created and will be sent along with every HTTP request to the server. The cookie is called .AspNetCore.Identity.Application and can be viewed with the Chrome developer tools – press F12 while running the demo web app in Chrome:

Authentication cookie after logging in onto .NET Core web application

We’ll continue with the logout process in the process in the next post. We’ll also add a small status bar to show whether the user is logged on or not.

View the list of MVC and Web API related posts here.

Advertisements

About Andras Nemes
I'm a .NET/Java developer living and working in Stockholm, Sweden.

2 Responses to Introduction to ASP.NET Core part 26: authorisation basics and logging in

  1. Bob Yuan says:

    Very good series. Thanks. One question, I am working on.net core web api and angular 2 project, and I will use IdentityServer 4 with token authentication, with ASP.NET Identity, can you provide some tutorial for that?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

ultimatemindsettoday

A great WordPress.com site

Elliot Balynn's Blog

A directory of wonderful thoughts

Robin Sedlaczek's Blog

Developer on Microsoft Technologies

HarsH ReaLiTy

A Good Blog is Hard to Find

Softwarearchitektur in der Praxis

Wissenswertes zu Webentwicklung, Domain-Driven Design und Microservices

the software architecture

thoughts, ideas, diagrams,enterprise code, design pattern , solution designs

Technology Talks

on Microsoft technologies, Web, Android and others

Software Engineering

Web development

Disparate Opinions

Various tidbits

chsakell's Blog

Anything around ASP.NET MVC,WEB API, WCF, Entity Framework & AngularJS

Cyber Matters

Bite-size insight on Cyber Security for the not too technical.

Guru N Guns's

OneSolution To dOTnET.

Johnny Zraiby

Measuring programming progress by lines of code is like measuring aircraft building progress by weight.

%d bloggers like this: