Introduction to ASP.NET Core part 27: showing the user status and logging out

Introduction

In the previous post we looked at the basics of authorisation and the log in process in .NET Core. The Authorization attribute which can be used for controllers and action methods helps us introduce a basic form of restrictions. The visitor must be logged in in order to reach the restricted sections in the application. The login process is handled by the built-in SignInManager service class. It offers a range of functions related to the user’s state. We also saw the importance of the return URL property which enables us to redirect the user to the page that they wanted to visit before logging in.

In this post we’ll look at how to log out and how to show the user’s status in the top section of each page.

The logout action method

The SignInManager object can easily handle the logout process. Its asynchronous SignOutAsync function will do exactly what we need. Add the following action method to the account controller:

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

We sign out the user and redirect them to the /books/index page.

The built-in User object

The ControllerBase abstract class which all our controllers ultimately derive from has access to a property called User of type System.Security.Claims.ClaimsPrincipal. I.e. it is not to be confused with the User class we built a couple of posts ago. This particular User property comes from MVC and is populated according to the status of the current user of the web surfing session. Both the controllers and the views have access to this object. It offers a number of useful functions that act as a shortcut instead of querying the database.

We can add some test code to the Index action method of BooksController:

[AllowAnonymous]
public IActionResult Index()
{
	Debug.WriteLine(string.Concat("User is authenticated: ", User.Identity.IsAuthenticated));
	Debug.WriteLine(string.Concat("User is an admin: ", User.IsInRole("Administrators")));
	Debug.WriteLine(string.Concat("User claims: ", string.Join("|", (from c in User.Claims select string.Concat("Key: ", c.Type, ", value: ", c.Value)))));
	return View(GetBookIndexViewModel());
}

…where Debug is located in the System.Diagnostics namespace. We check whether the user is authenticated, whether they have an Administrator role and what their claims are.

Since I’m still logged in from the previous post I got the following output in the Output window:

User is authenticated: True
User is an admin: False
User claims: Key: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier, value: 16b9d98d-5689-4718-9214-6292803bd826|Key: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name, value: andras.nemes|Key: AspNet.Identity.SecurityStamp, value: 5cc90ef1-52e7-4893-a6a9-2670e77c65c8

If you’re logged out then you’ll see something like the following:

User is authenticated: False
User is an admin: False
User claims:

…i.e. the claims list is empty.

As mentioned before in this series if you are new to claims then start here. The “nameidentifier” claim is the user ID and “name” is the user name.

We’ll use this User object in the shared _Layout view to show the user’s status and the login and register buttons. Before we do that we’ll make two modifications in the Account controller. The first in the POST Login action method where we have a bug. The ReturnUrl property won’t always be set. If the user goes directly to /account/login then userLoginViewModel.ReturnUrl will be null. Hence we’ll add a guard clause and redirect the user to the /books/index page:

[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)
		{
			if (!string.IsNullOrEmpty(userLoginViewModel.ReturnUrl))
			{
				return Redirect(userLoginViewModel.ReturnUrl);
			}
			else
			{
				return RedirectToAction("Index", "Books");
			}
		}
	}
	ModelState.AddModelError("", "Login failure. Please check your username and password");
	return View(userLoginViewModel);
}

The other change is that we want to sign in the user immediately after a successful registration. If we don’t do that then the user signs up and then will have to sign in as a separate step which is degrades the usability of our site. Here’s the modified POST Register method:

[HttpPost, ValidateAntiForgeryToken]
public async Task<IActionResult> Register(UserRegistrationViewModel registrationViewModel)
{
	if (ModelState.IsValid)
	{
		User newUser = new User()
		{
			UserName = registrationViewModel.Username,
			YearOfBirth = registrationViewModel.YearOfBirth
		};
		IdentityResult userCreationResult = 
			await _userManager.CreateAsync(newUser, registrationViewModel.Password);
		if (userCreationResult.Succeeded)
		{
			await _signinManager.PasswordSignInAsync(newUser.UserName, registrationViewModel.Password, false, false);
			return RedirectToAction("Index", "Books");
		}
		else
		{
			IEnumerable<string> userCreationErrors = from e in userCreationResult.Errors select e.Description;
			string concatenatedErrors = string.Join(Environment.NewLine, userCreationErrors);
			ModelState.AddModelError("", concatenatedErrors);
		}
	}
	return View();
}

The layout with some user information

We can now modify the shared _Layout.cshtml view file. Add the following div just after the body tag:

<div>
    @if (User.Identity.IsAuthenticated)
    {
        <span>Welcome @User.Identity.Name</span>
        <form method="post" asp-controller="Account" asp-action="Logout" asp-antiforgery="true">
            <input type="submit" value="Log out" />
        </form>
    }
    else
    {
        <a asp-controller="Account" asp-action="Register">Register</a>
        <a asp-controller="Account" asp-action="Login">Log in</a>
    }
</div>

If the user is authenticated then we show the user’s name and a logout button. Otherwise we show the log in and register links.

Run the application and test as much as you like: log in, log out and register new users.

We’ve actually reached the end of the series. We’ll summarise our findings in the next post.

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.

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

iReadable { }

.NET Tips & Tricks

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: