ASP.NET MVC6 Identity Part 2 – WebAPI

I’ve got this vague thought at the moment. To make my web site truly responsive I need to lose the round trip to the server to load the login page. With that in mind, I’m investigating adding a WebAPI to my application to handle logging in.

The idea is relatively simple. The layout (and hence the home page) will contain the login form. When the user clicks on the login button to submit the form, an AJAX request is sent to the server to validate the response. The server will log the person in and send back whatever is required to actually authenticate the user. The application then stores this (in a cookie or something similar) and refreshes the page. If the user is not authenticated, then immediate feedback can be provided in terms of the validation errors.

Of course, this requires that I provide a WebAPI. I’m going to create a new AccountController class in Areas/Public/Controllers for this purpose. Fortunately, I don’t need any additional packages – the WebAPI stuff is included in MVC6.

My First WebAPI

Let’s start with a pretty basic example – in fact, let’s make it as basic as I can:

using Microsoft.AspNet.Mvc;

namespace Grumpy.Wizards.Areas.Public.Controllers
{
    public class AccountController : Controller
    {
        [Route("api/login")]
        [HttpPost]
        public IActionResult Login([FromBody] LoginViewModel model)
        {
            return new HttpStatusCodeResult(200);
        }
    }
}

This listens on the route /api/login and returns a 200 response to any POST request. I’m not validating the request at this point. I want to get to the point where I can send requests and receive a response back.

if you are confused by the terminology, requests and response codes, you should read a primer – I like the Tuts+ primer.

To test this, I will run the application and then bring up Postman for the testing of the service. Here is a look at the interface configured to send the request:

blog-code-0602-1

I’ve included the actually body as well. Although I don’t check it with the code right now, I’m going to be using that soon so I may as well get the request correct now.

Validating the Model

Of course, this API doesn’t do anything other than respond. To do more, we need to validate the request. The logic is simple enough. If the model is good, then return a 200 response. If the model is bad, return a 400 Bad Request response and a JSON response that provides details on why the model failed validation.

In the ASP.NET code, I use ModelState.IsValid to determine if the model follows the rules I’ve set. I introduced the LoginViewModel and its annotations that implement the validation in my last article. I need a little more code to return those validation errors to the front-end application:

using Grumpy.Wizards.Areas.Public.ViewModels;
using Microsoft.AspNet.Mvc;

namespace Grumpy.Wizards.Areas.Public.Controllers
{
    public class AccountController : Controller
    {
        [Route("api/login")]
        [HttpPost]
        public IActionResult Login([FromBody] LoginViewModel model)
        {
            if (!ModelState.IsValid)
            {
                return new BadRequestObjectResult(ModelState);
            }

            return new HttpStatusCodeResult(200);
        }
    }
}

Let’s try getting through this with a non-valid model. The model uses the ApplicationUser validator that returns success if the provided value is RFC-5321 or our admin user, so I’m going to use a non-compliant username to test the validation:

blog-code-0602-2

If I change back to “admin” as the username or I put in a valid email address, I get the 200 Success response back. This means my data validation is working.

The second part of the process is verification and that’s where ASP

using System.Threading.Tasks;
using Grump.yWizards.Areas.Public.ViewModels;
using Grumpy.Wizards.Database;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Mvc;

namespace Grumpy.Wizards.Areas.Public.Controllers
{
    public class AccountController : Controller
    {
        private UserManager userManager = null;
        private SignInManager signInManager = null;

        public AccountController(UserManager userManager, SignInManager signInManager)
        {
            this.userManager = userManager;
            this.signInManager = signInManager;
        }

Next we want to authenticate the user. This is the same logic that we used the last time we covered Identity:

        /// <summary>
        /// Authenticate the supplied user
        /// </summary>
        /// The Login ViewModel
        /// Result of the login action
        [Route("api/login")]
        [HttpPost]
        [AllowAnonymous]
        public async Task Login([FromBody] LoginViewModel model)
        {
            // Verify that the model is valid according to the validation rules
            // in the model itself.  If it isn't valid, return a 400 Bad Request
            // with some JSON reviewing the errors
            if (!ModelState.IsValid)
            {
                return new BadRequestObjectResult(ModelState);
            }

            // Find the user in our database.  If the user does not exist, then
            // return a 400 Bad Request with a general error.
            var user = await userManager.FindByNameAsync(model.Email);
            if (user == null)
            {
                ModelState.AddModelError("", "Invalid Username or Password");
                return new BadRequestObjectResult(ModelState);
            }
            // If the user has not confirmed his/her email address, then return a
            // 400 Bad Request with a request to activate the account.
            if (!user.EmailConfirmed)
            {
                ModelState.AddModelError("Email", "Please activate your account");
                return new BadRequestObjectResult(ModelState);
            }
            // Authenticate the user with the Sign-In Manager
            var result = await signInManager.PasswordSignInAsync(model.Email, model.Password, false, shouldLockout: false);
            // If the authentication failed, add the same error that we add when
            // we can't find the user (so you can't tell the difference between
            // a bad username and a bad password) and return a 400 Bad Request
            if (!result.Succeeded)
            {
                ModelState.AddModelError("", "Invalid Username or Password");
                return new BadRequestObjectResult(ModelState);
            }

            return new HttpStatusCodeResult(200);
        }

After doing a few tests, I got this:

blog-code-0602-3

Oops – this actually is a security concern. I give away the fact that the user has an account even though the password is wrong. To fix this, I moved the EmailConfirmed below the sign in test. This does bring up an interesting problem – I need to activate the admin account when I’m creating the database. To do this, I adjust the ApplicationDbContext.cs file:

                user = new ApplicationUser {
                    UserName = options.DefaultUsername,
                    EmailConfirmed = true
                };

After I deleted the database and restart the server, I could get all the way through the WebAPI. You need the Postman Interceptor installed to see cookies, but once you do that, you’ll get the following:

blog-code-0602-4

There are actually three cookies generated:

  1. .AspNet.Microsoft.AspNet.Identity.Application
  2. 8lk97XBCwt0 (or more correctly, something completely random)
  3. connect.sid

All I need to do in my web application is to transcribe those cookies that I received to my web session and refresh the page. The ASP.NET application should then recognize me as authenticated as a particular user and display the Signoff link instead of the Signon link.

In my next article, I’m going to use this WebAPI to do exactly that inside of the ASP.NET Layout.

Note that there is nothing different about these techniques from the last time I did Identity. If you want to do Identity the same way I did last time, please feel free – there is no difference.