Most web applications need some sort of user management system, that allows it to sort out which User should be allowed to access given parts of the application. This can be split into the topics of registration, authentication and authorization.

Where registration allows a User to associate themselves with your application. Authentication checks if the User is really what he claims to be. And Authorization checks, based on some rule set, if a User is allowed to access given parts of the application.

Note that Authentication and Authorization are two orthogonal and independent concepts. There is a lot of confusion (maybe because their names are quite similiar ;-)), yet it is a pretty clear distinction and this will become clear in the posts about Asp Identity. Actually AspNet core uses the Authorize Attribute, as we will soon see, for both and IMHO therefore contributes to this confusion.

All of this three topics are handled by ASP.Net Identity.

ASP.Net Identity is essentially an API for user management, where User data is stored in an sql database with the help of Entity Framework Core. It presents a lot of out of the box functionality for User management.

AspNet Identity is applied with services and middleware, like most other configurable services in AspNet.
As you might have guessed it is a vast topic, and although it comes with sensible defaults, you can customize pretty much everything with it (like in most of AspNet).

What this post will cover

  • Set up User management with ASP Identity

What this post will not cover

  • Authentication
  • Authorization
  • Middleware rewriting

How to set up asp net core identity

The setup of Identity touches pretty much all of the application:

  • Model classes
  • Config changes
  • Controllers & actions to support authentication and authorization operations.
  • Controllers for User registration
  • Registration with third party authentication services (like google Auth etc.)

So the series about Identity is split into three (or maybe eventually more) parts:

  1. User management with an AccountController (this)
  2. Authentication with the Authorize attribute
  3. And Authorization with Roles and corresponding parameters in Authorize Attribute

To add Users to our application, we need to take the following steps. I will explain them in some detail (depending on the topic) in this post:

  1. Create a User class derived from IdentityUser
  2. Setup a database with EF core
  3. Create an AdminController that handles the User management Requests with CRUD operations.

We start by setting up a User class.

Create a User Class

To Start of with User Management wer First Need a User Model entity. For this we derive from the IdentityUser class in the Microsoft.AspNetCore.Identity namespace.
The IdentityUser is a basic User representation, which can easily be extended by adding properties to the derived class. We will explore how to create custom users with another blog post.

The basic properties of the IdentityUser class are:

  • Id
  • Name
  • Email
  • PhoneNumber
  • Claims
  • Logins
  • PasswordHash
  • Roles
  • SecurityStamp

The derived Model would look like this:

namespace MyWebApp.Models
{
public class MyUser : IdentityUser 
{
// blank, because we only use basic properties
}
}

The next thing we need to do is to set up a database context with Ef core:

using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
namespace MyWebApp.Models 
{
public class MyWebAppIdentityDbContext : IdentityDbContext 
{    
public MyWebAppIdentityDbContext(DbContextOptions options)
: base(options) { }
} 
}

We do not override the Configure method, because the basic functionality is enough for the intro.

As a next step we need to set up and configure Identity in the Startup.cs:

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using MyWebApp.Models;
namespace MyWebApp 
{
public class Startup 
{
public Startup(IConfiguration configuration) =>
Configuration = configuration;
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services) 
{
services.AddDbContext<MyWebAppIdentityDbContext>(options =>
options.UseSqlite("./yoursqlite.db"));
services.AddIdentity<MyUser, IdentityRole>()
.AddEntityFrameworkStores()
.AddDefaultTokenProviders();
services.AddMvc();
}
public void Configure(IApplicationBuilder app)
{
app.UseStatusCodePages(); 
app.UseDeveloperExceptionPage();
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvcWithDefaultRoute();
}
}
}
  1. Setup EntityFramework (services.adddbcontext)
  2. Setup services for Identity with services.AddIdentity (what IdentityRole is will become clear in the post about Authorization)
  3. Final change adds to middleware pipeline, associates requests with users based on cookies and URL rewriting

You will also need to migrate Entity Framework, but we want to focus on User management here, therefore we look not into it.

Use Identity

To use Identity in our example application we create a centralized user administration. For this we create an AdminController that handles User Accounts. With the conclusion of this post we will have created a controller that enables us to perform CRUD operations for User Accounts with our Identity database. First view on the AdminController:

using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Users.Models;
namespace MyWebApp.Controllers
{
public class AdminController : ControllerBase
{
private readonly UserManager<MyUser> _userManager;
public AdminController(UserManager<MyUser> userManager)
{
_userManager = userManager;
}
public ViewResult Index() => View(_userManager.Users);
}
}

Access to data store is provided by UserManager<T> object, you can think of it as an implementation of the Repository pattern. It allows you CRUD operations on the User database we registered with EF core.

The Index method of the AdminController returns a view that simply enumerates the Users, but I skipped the necessary html, because it does not add much value to the idea I try to convey here.

With these methods we can not yet manage Users, for this we first need to create a DTO to structure our data via modelbinding.

Create a User account

To create User accounts we create a CreateDTO that contains all the values that are expected to create a User. I also could simply use parameters that bind to the form values, yet I find this to be more explicit and better to use (later with update I will use the other method just to show you that it works too)

namespace MyWebApp.Models
{
public class MyModelCreateDTO
{
[Required]
public string Name {get; set;}
[Required]
public string Email {get; set;}
[Required]
public string Password {get; set;}
}
}

Then we add the following methods to the AdminController.

...
public ViewResult Create() => View();
[HttpPost]
public async Task Create(MyModelCreateDTO model)
{
if (ModelState.IsValid)
{
var user = new MyUser
{
UserName = model.Name,
Email = model.Email
};
IdentityResult result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
return RedirectToAction("Index");
else       
foreach (IdentityError error in result.Errors)
ModelState.AddModelError("", error.Description);        
}
return View(model);
}
...

Both methods are called Create, yet one simply returns a View with a form on it (again omitted the html), that allows us to fill the properties of the CreateDTO via modelbinding. When this form is submitted, the values are used in the Create method which is decorated with the HttpPost Attribute.

If the ModelState is valid, we create a new MyUser with the data that was submitted via the posted form. We then retain an IdentityResult from the UserManager<T> class (remember this is a repository style storage access class). On creation of a user the UserManager<T> checks among other things, if the password is valid (like if there is already a user with the same properties etc.) Those validations can easily be configured in the Starup.cs.

The returned IdentityResult tells you with its Succeeded property if the User is Valid. If so the Application Redirects to the Index method were all Users are simply displayed. (see Post/Redirect/Get pattern)

If it does not succeed, the user is informed with the ModelState.AddModelError, which can be displayed in an appropriate .cshtml file.

Short view on Password validation

The password policy can be set individually where the default policy entails the following values:

  • one non alphanumeric sign
  • one digit 0-9
  • one uppercase letter (A-Z)

But it can be configured for company policies or such, like so:

services.AddIdentity<MyUser, IdentityRole>(options =>
{
options.Password.RequiredLength = 6;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireLowercase = false;
options.Password.RequireUppercase = false;
options.Password.RequireDigit = false;
})
.AddEntityFrameworkStores()
.AddDefaultTokenProviders();

Account validation policy

The UserManager<T> validates Users on Usernames and Email addresses to avoid the duplication of a User. This also can be defined in the Startup.cs

It can be configured in the Startup.cs with the IdentityOptions.User property, that returns a UserOptions instance. UserOptions has the two properties:

  • AllowedUserNameCharacters
    • specifies legal characters for a username (by default):
      • a-z
      • A-Z
      • 0-9
      • hyphen (-)
      • period(.)
      • underscore (_)
      • and @
  • RequireUniqueEmail bool
    • allows or disallows email duplication

Every character needs to be defined expliciti

Delete a User

When you are allowed to create an account you obviously should also be able to delete an account. (it is good software practice to create always the opposite operation like add/remove, create/delete, show/hide etc.)

Also as you might expect from a Repository, the UserManager<T> class has a DeleteAsync method built in, that works quite similar to the CreateAsync method.

For a delete method we will add the following methods to the AdminController :

[HttpPost]
public async Task DeleteAsync(string id)
{
MyUser user = await _userManager.FindByIdAsync(id);
if (user != null)
{
IdentityResult result = await _userManager.DeleteAsync(user);
if (result.Succeeded)
return RedirectToAction("Index");
else
AddErrorsFromResult(result);
}
else 
ModelState.AddModelError("", "User Not Found");
return View("Index", userManager.Users);
}
private void AddErrorsFromResult(IdentityResult result)
{
foreach (IdentityError error in result.Errors)
ModelState.AddModelError("", error.Description);
}

To delete a User we first need to find one by using the FindByIdAsync method. We get the Id from the post methods parameter. (either by form value or if you use an api controller by the message body). Again this works from asp’s model binding.

The result from FindById is again an IdentityResult. This works as with the object that is returned from CreateAsync method, and it is processed in the same manner (checking Succeeded property, and if true redirect etc.).

Update a User Account

Last but not least, the User management system needs an update method. With all CRUD like Repositories we also need an Update method.

To do so we add to the AdminController the following fields and constructor parameters:

...
private IUserValidator _userValidator;
private IPasswordValidator _passwordValidator;
private IPasswordHasher _passwordHasher;
public AdminController(UserManager<MyUser> userManager, IUserValidator userValidator,
IPasswordValidator passwordValidator, 
IPasswordHasher passwordHash) 
{
_userManager = userManager;
_userValidator = userValidator;
_passwordValidator = passwordValidator;
_passwordHasher = passwordHash;
}

Also we need to implement the following two methods:

public async Task UpdateAsync(string id) 
{
MyUser user = await _userManager.FindByIdAsync(id);
if (user != null)
return View(user);
else 
return RedirectToAction("Index");
}
[HttpPost]
public async Task UpdateAsync(string id, string email, string password)
{
MyUser user = await userManager.FindByIdAsync(id);
if (user != null)
{
user.Email = email;
IdentityResult validEmail = await _userValidator.ValidateAsync(userManager, user);
if (!validEmail.Succeeded)
AddErrorsFromResult(validEmail);
IdentityResult validPass = null;
if (!string.IsNullOrEmpty(password))
{
validPass = await _passwordValidator.ValidateAsync(userManager, user, password);
if (validPass.Succeeded)
user.PasswordHash = passwordHasher.HashPassword(user, password);
else
AddErrorsFromResult(validPass);
}
if ((validEmail.Succeeded && validPass == null) || (validEmail.Succeeded && password != string.Empty && validPass.Succeeded))
{
IdentityResult result = await _userManager.UpdateAsync(user);
if (result.Succeeded)
return RedirectToAction("Index");
else
AddErrorsFromResult(result);
}
}
else
ModelState.AddModelError("", "User Not Found");
return View(user);
}

We have two Update methods, the Get style method simply presents the User if one is found (this is because we had only a getall representation to this point). The second method (HttpPost) actually updates a User.

There is a lot going on in the second UpdateAsync method. First we check if the values coming in from model binding lead to a User in the storage, again by using the FindByIdAsync method from the UserManager<T> class.

We then use the _userValidator instance, that we get from DI in the controllers ctor and setup in the configure method, and call its ValidateAsync method. This then again returns an IdentityResult object instance. We check if it Succeeded and if not, add errors to the Model state.

The same process is applied to the password (with a simple validation check from the posted values).

To the end we check if both the email and the password are valid and if so use the _userManager ‘s UpdateAsync method to update the user. Which also returns an IdentityResult and is also tested for success (as we have done several times in the other methods).

The Update (and Read) complete the AdminControllers CRUD functionality. To test this, you’d need some View files which you could do yourself ;-).

With this AdminController to manage the Users of our application we will look into Authentication in the next post.

Summary
In this post we shortly examined what AspNet core Identity essentially does and how we would go about implementing a Controller that can manage the Users of our application.

In upcoming posts on Identity, we will look at basic authentication, Authorization, two factor authentication and others.


0 Comments

Leave a Reply

Avatar placeholder