Authentication In An ASP.NET Core API - Part 2: Identity, Access Granted

In this series, I am going to outline some basic approaches to authenticating your .NET Core API using either ASP.NET Core Identity or token-based authentication with a JSON Web Token (JWT). I will also explore how to configure your application to return proper response types to both Redirect To Login and Redirect To Access Denied events when using ASP.NET Core Identity.

Authentication In A Dot Net Core API

  1. ASP.NET Core Identity, Accessed Denied.
  2. ASP.NET Core Identity, Accessed Granted.
  3. Token based authentication with a JSON Web Token (JWT).

In part 1 (ASP.NET Core Identity, Accessed Denied) of this series, I explored how to deny access to your ASP.NET Core API endpoints and return proper response types to both the Redirect To Login and Redirect To Access Denied events using ASP.NET Core Identity. In this post, I will show you how to register a user with ASP.NET Core Identity and authenticate that user so that it will have access to our now secure endpoints.

Registering A User

When we set up our DB Context to derive from a IdentityDbContext type and created the database through our Entity Framework migrations, we were provide with the necessary tables needed to support our Identity configuration.

Database Tables

To register a user and populate these tables with the data needed to authenticate, I created the following endpoint in a new controller called AccountApiController located in the Admin area of my project.

[HttpPost]
public async Task<IActionResult> Create([FromBody] AccountRegisterLogin model)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState.Values.SelectMany(v => v.Errors).Select(modelError => modelError.ErrorMessage).ToList());
    }

    var user = new UserEntity { UserName = model.Email, Email = model.Email };
    var result =  await _userManager.CreateAsync(user, model.Password);

    if (!result.Succeeded)
    {
        return BadRequest(result.Errors.Select(x => x.Description).ToList());
    }

    await _signInManager.SignInAsync(user, false);

    return Ok();
}

We start by passing a representative of the AccountRegisterLogin to this endpoint. Of which looks like this.

{
	"email": "[email protected]",
	"password": "youpassword1"
}

This payload will serialize to the AccountRegisterLogin model.

public class AccountRegisterLogin
{
    [Required]
    [EmailAddress]
    [Display(Name = "Email")]
    public string Email { get; set; }

    [Required]
    [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
    [DataType(DataType.Password)]
    [Display(Name = "Password")]
    public string Password { get; set; }
}

In the Create method, we are...

  1. Checking the Model State to ensure the data populating our model is valid.
    • If it is not valid, return our model validation errors.
  2. Create a mapping to our UserEntity object.
  3. Make an asynchronous request to our UserManager<UserEntity> to create a new user record.
  4. Check the result for success.
    • If it is not valid, return our UserManager service errors.
  5. Make an asynchronous request to our SignInManager<UserEntity> object to sign that user in.

That is it for registration!

Authenticating

Now that we have a user record in our tables, we need to create a method to log in that user. Of which will again live in the AccountApiController located in the Admin area of my project.

[HttpPost("login")]
public async Task<IActionResult> Login([FromBody] AccountRegisterLogin model)
{
    if (!ModelState.IsValid)
    {
        return BadRequest();
    }

    var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, isPersistent: false, lockoutOnFailure: false);

    if (!result.Succeeded)
    {
        return BadRequest();
    }

    return Ok();
}

In this method, we again pass in our AccountRegisterLogin request and start by checking to see if the state of that Model is valid. If it is, we make a request to our SignInManager<UserEntity> object to attempt to sign in that user with the Model's current properties. If that request succeeded, we return a status of 200 Ok.

During the sign in request, when successful the necessary cookie needed to authenticate with Identity is passed back as part of the response.

Identity Cookie

With this cookie set on the client, we now have everything we need to authenticate with Identity. So long as the cookie is current, all future requests to our API endpoints that are guarded with the [Authorize] will be authenticated.

This is it! We now have a way to register a user and log that user in while supplying the necessary cookie. In the next part of this series, I will talk about adding token authentication to my blog using JSON Web Tokens. Until then, feel free to leave any comments you might have below.