ASP.Net

Extending Identity Accounts and Implementing Role-Based Authentication in ASP.NET MVC 5


Image by: Elif Ayiter | Some Rights Reserved

In a previous post, we took a quick look at extending Identity Accounts in the context of the new Identity system under ASP.NET MVC 5. We also examined the basics of Entity Framework Code-First Migrations. If you haven’t read that article, you might want to do so now, so we don’t have to repeat some of the general ideas explained there.

If you are using the Identity 2.0 Framework:

This article focuses on customizing and modifying version 1.0 of the ASP.NET Identity framework. If you are using the recently released version 2.0, this code in this article won’t work. For more information on working with Identity 2.0, see ASP.NET Identity 2.0: Understanding the Basics and ASP.NET Identity 2.0: Setting Up Account Validation and Two-Factor Authentication.

Many of the customizations implemented in this article are included “ini the box” with the Identity Samples project. I discuss extending and customizing IdentityUser and IdentityRole in Identity 2.0 in a new article, ASP.NET Identity 2.0: Customizing Users and Roles

If you are using the Identity 1.0 Framework:

For the purpose of this post, we are going to look at a implementing relatively simple role-based authentication and identity management for an ASP.NET MVC 5 web application. The examples used will be deliberately simplified, and while they will effectively illustrate the basics of setting up role-based identity management, I can promise that the implementation here will lack certain things we would like to see in a production project (such as complete exception handling). Also, production application modeling would likely look a little different based upon business needs.

In other words, I’m keeping it simple, much like we ignored the effects of friction way back in high school physics. For a more advanced look at working with roles, and more granular application permissions management using Group – based permissions, see Extending and Modifying Roles and   ASP.NET MVC 5 Identity: Implementing Group-Based Permissions Management.

That said, there’s a lot to cover. The article is not as long as it seems, because I am including some large code samples, and images to illustrate what’s going on. The complete project source code for the project discussed in this article can be found at my Github Repo.

Download the Source Code

The complete project source code for this article is available at my Gihub Repo. NOTE: You will need to enable Nuget Package Restore in order to build the project properly.

Basic Hypothetical Application Requirements

We will assume our identity management needs to do the following for a Line-of-Business web application used primarily by internal users or others suitably authorized by management. The application will have a minimal public-facing interface, and will require all users to log-in to access even minimal functionality (it is easy to extend what we will be doing to include public users, but this is derived from an actual application I needed to create rather quickly at work).

  • User accounts must be created by one or more Admin-level users. “Registration” as we know it from the ASP.NET Membership paradigm, does not exists (anonymous users cannot register and create accounts from the public site).
  • User Identity accounts will be extended to include an email address, and first/last names
  • For our purposes, there will be at least three Roles; Administrator (full access to everything), Editor (can perform most business functions of the application, but cannot access admin functions such as account management), and Read-Only User (what the name implies).
  • Each user may be a member of zero or more roles. Multiple roles will have the access rights of all the roles combines.
  • To keep things simple, roles are independently defined, and are not members of other roles.
  • All application roles are pre-defined. There is no administrative creation of roles. Also, Role permissions are integral to the application, and not manageable by administrators (this is for simplicity at this point).
  • Anonymous access to the site is not allowed, except to the log-in portal.
  • There will be no use of external log-ins or OAuth (code for this is included as part of the default MVC project. We will remove it to keep things clean).

Getting Started – Stripping Unneeded Items from the MVC Project Template

While we could start with what we created in the previous article on Extending Identity Accounts, we will be re-arranging things sufficiently it will be cleaner for our purposes to start fresh. Create a new ASP.NET MVC project in Visual Studio. Before we do anything else, let’s clear out some unneeded clutter so we are left with only what we need.

We are going to be removing a bunch of code related to managing external log-ins, as well as clearing out some of the extraneous comments included with the default project (which to me, just add noise to our code).

We will start with the AccountController.

Simplifying AccountController – Remove the Clutter

There are a number of m on AccountController related to External Logins which we don’t need. If you examine AccountController carefully, you can go through and delete the code for the following methods:

  • Dissociate()
  • ExternalLogin()
  • ExternalLoginCallback()
  • LinkLogin()
  • LinkLoginCallback()
  • ExternalLoginConfirmation()
  • ExternalLoginFailure()
  • RemoveAccountList()

Additionally, if you look closely, there is a code #region (I know. I hate #region and think it should be done away with) for helpers. From here, we can delete the following items:

  • The member constant XsrfKey
  • The entire class ChallengeResult

At this point, we can also right-click in the code file and select Remove and Sort Usings to clear out some of the unneeded clutter here as well.

At this point, our AccountController.cs file should contain the following methods (reduced to simple stubs here for brevity – we’ll get to the code shortly:

The Cleaned Up AccountController.cs File (Stubs Only – Code Hidden for Brevity):
using AspNetRoleBasedSecurity.Models;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.Owin.Security;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
  
namespace AspNetRoleBasedSecurity.Controllers
{
    [Authorize]
    public class AccountController : Controller
    {
        public AccountController()
            : this(new UserManager<ApplicationUser>(
            new UserStore<ApplicationUser>(new ApplicationDbContext())))
        {
        }
  
  
        public AccountController(UserManager<ApplicationUser> userManager)
        {
            UserManager = userManager;
        }
  
  
        public UserManager<ApplicationUser> UserManager { get; private set; }
  
  
        [AllowAnonymous]
        public ActionResult Login(string returnUrl)
        {
            ViewBag.ReturnUrl = returnUrl;
            return View();
        }
  
  
        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
        {
             // . . . Code Hidden for brevity
        }
  
  
        [AllowAnonymous]
        public ActionResult Register()
        {
            return View();
        }
  
  
        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public async Task<ActionResult> Register(RegisterViewModel model)
        {
             // . . . Code Hidden for brevity
        }
  
  
        public ActionResult Manage(ManageMessageId? message)
        {
             // . . . Code Hidden for brevity
        }
  
  
        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<ActionResult> Manage(ManageUserViewModel model)
        {
             // . . . Code Hidden for brevity
        }
  
  
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult LogOff()
        {
            AuthenticationManager.SignOut();
            return RedirectToAction("Index", "Home");
        }
   
  
        protected override void Dispose(bool disposing)
        {
             // . . . Code Hidden for brevity
        }
  
  
  
  
        #region Helpers
  
  
        private IAuthenticationManager AuthenticationManager
        {
             // . . . Code Hidden for brevity
        }
  
  
        private async Task SignInAsync(ApplicationUser user, bool isPersistent)
        {
             // . . . Code Hidden for brevity
        }
  
  
        private void AddErrors(IdentityResult result)
        {
             // . . . Code Hidden for brevity
        }
  
  
        private bool HasPassword()
        {
             // . . . Code Hidden for brevity
        }
  
  
        public enum ManageMessageId
        {
             // . . . Code Hidden for brevity
        }
  
  
        private ActionResult RedirectToLocal(string returnUrl)
        {
             // . . . Code Hidden for brevity
        }
  
  
        #endregion
    }
}

Remove Unneeded Views

Along with the unnecessary Controller methods we just removed, we can also remove the unnecessary views related to external logins. If we open the Views => Account folder in Solution Explorer, we find we can delete the highlighted views below from our project:

Solution Explorer – Remove Unneeded Views:

solution-explorer-remove-unneeded-views

Now that the totally unnecessary views are out of the way, let’s remove the External Log-in code from the remaining views as well.

Clean Up Account-Related Views

There is some remaining clutter to clear out of our Account-related views. We don’t want dead-end links on our site, and we want to keep only relevant code in our views.

We can start with the Login.cshtml file, which contains a section related to creating an external log-in from various social networks (highlighted in yellow).

Login.cshtml – Remove Social Network Login Option:
@{
    ViewBag.Title = "Log in";
}
<h2>@ViewBag.Title.</h2>
<div class="row">
    <div class="col-md-8">
        <section id="loginForm">
            @using (Html.BeginForm("Login", "Account", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
            {
                @Html.AntiForgeryToken()
                <h4>Use a local account to log in.</h4>
                <hr />
                @Html.ValidationSummary(true)
                <div class="form-group">
                    @Html.LabelFor(m => m.UserName, new { @class = "col-md-2 control-label" })
                    <div class="col-md-10">
                        @Html.TextBoxFor(m => m.UserName, new { @class = "form-control" })
                        @Html.ValidationMessageFor(m => m.UserName)
                    </div>
                </div>
                <div class="form-group">
                    @Html.LabelFor(m => m.Password, new { @class = "col-md-2 control-label" })
                    <div class="col-md-10">
                        @Html.PasswordFor(m => m.Password, new { @class = "form-control" })
                        @Html.ValidationMessageFor(m => m.Password)
                    </div>
                </div>
                <div class="form-group">
                    <div class="col-md-offset-2 col-md-10">
                        <div class="checkbox">
                            @Html.CheckBoxFor(m => m.RememberMe)
                            @Html.LabelFor(m => m.RememberMe)
                        </div>
                    </div>
                </div>
                <div class="form-group">
                    <div class="col-md-offset-2 col-md-10">
                        <input type="submit" value="Log in" class="btn btn-default" />
                    </div>
                </div>
                <p>
                    @Html.ActionLink("Register", "Register") if you don't have a local account.
                </p>
            }
        </section>
    </div>
    <div class="col-md-4">
        <section id="socialLoginForm">
            @Html.Partial("_ExternalLoginsListPartial", new { Action = "ExternalLogin", ReturnUrl = ViewBag.ReturnUrl })
        </section>
    </div>
</div>
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

In the above, the section highlighted in yellow can be deleted.

Next, let’s remove similar External Login functionality from the Manage.cshtml file (highlighted in yellow, again):

Manage.cshtml – Remove External Login Items:
@using AspNetRoleBasedSecurity.Models;
@using Microsoft.AspNet.Identity;
@{
    ViewBag.Title = "Manage Account";
}
  
<h2>@ViewBag.Title.</h2>
  
<p class="text-success">@ViewBag.StatusMessage</p>
<div class="row">
    <div class="col-md-12">
        @if (ViewBag.HasLocalPassword)
        {
            @Html.Partial("_ChangePasswordPartial")
        }
        else
        {
            @Html.Partial("_SetPasswordPartial")
        }
  
        <section id="externalLogins">
            @Html.Action("RemoveAccountList")
            @Html.Partial("_ExternalLoginsListPartial", new { Action = "LinkLogin", ReturnUrl = ViewBag.ReturnUrl })
        </section>
    </div>
</div>
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

As before, the highlighted area can be safely removed.

Remove Unneeded Model Classes

As with the previous sections, there are unneeded model classes we can safely dispose of in order to clean up our project. If we expand the Models folder in Solution Explorer, we find there is a single code file, AccountViewModels.cs,  containing several ViewModel classes related to Identity Management. Review the file carefully, and delete the ExternalLogInConfirmationViewModel item highlighted in yellow below:

Account View Models File – Remove Unneeded Classes:
using System.ComponentModel.DataAnnotations;
namespace AspNetRoleBasedSecurity.Models
{
    public class ExternalLoginConfirmationViewModel
    {
        [Required]
        [Display(Name = "User name")]
        public string UserName { get; set; }
    }
  
    public class ManageUserViewModel
    {
        [Required]
        [DataType(DataType.Password)]
        [Display(Name = "Current password")]
        public string OldPassword { get; set; }
  
        [Required]
        [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
        [DataType(DataType.Password)]
        [Display(Name = "New password")]
        public string NewPassword { get; set; }
  
        [DataType(DataType.Password)]
        [Display(Name = "Confirm new password")]
        [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
        public string ConfirmPassword { get; set; }
    }
  
    public class LoginViewModel
    {
        [Required]
        [Display(Name = "User name")]
        public string UserName { get; set; }
        [Required]
        [DataType(DataType.Password)]
        [Display(Name = "Password")]
        public string Password { get; set; }
        [Display(Name = "Remember me?")]
        public bool RememberMe { get; set; }
    }
  
    public class RegisterViewModel
    {
        [Required]
        [Display(Name = "User name")]
        public string UserName { get; set; }
  
        [Required]
        [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
        [DataType(DataType.Password)]
        [Display(Name = "Password")]
        public string Password { get; set; }
  
        [DataType(DataType.Password)]
        [Display(Name = "Confirm password")]
        [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
        public string ConfirmPassword { get; set; }
    }
}

Extending the Identity Management Models and View Models

As we have seen, the Model classes used by our application to manage identity and authorization are contained in the IdentityModels.cs file. Additionally, there are Identity-related ViewModel classes defined in the AccountViewModels.cs file used to manage the transfer of identity data between our views and controllers.

Important to note here that we would really like to get all of our model definitions correct before we run the application and create any new user accounts, or register using the normal (and soon-to-be-removed) MVC “registration” mechanism. We are going to use Entity Framework Migrations and Code-First to do the Database heavy-lifting for us. While it is not terribly difficult to update our models later (and hence, the database, through EF migrations), it is cleaner and smoother to get it right up front.

In order to conform to our project specifications, one of the first things we need to do is extend the default ApplicationUser class to include Email, LastName, and FirstName properties. Open the IdentityModels.cs file. Currently, the code should look like this:

The Default IdentityModels File with Emply ApplicationUser Stub:
using Microsoft.AspNet.Identity.EntityFramework;
  
namespace AspNetRoleBasedSecurity.Models
{
    public class ApplicationUser : IdentityUser
    {
    }
  
  
    public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
    {
        public ApplicationDbContext()
            : base("DefaultConnection")
        {
        }
    }
}

In this step, we are going to extend our ApplicationUser class to include the properties required by our application specification. Also, we will add an IdentityManager class in which we consolidate our user and role management functions. We’ll discuss how all this works in a moment. For now, add the following code to the IdentityModels.cs code file (note we have added some new using statements at the top as well):

Modified IdentityModels.cs File
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.AspNet.Identity;
using System.ComponentModel.DataAnnotations;
using System.Collections.Generic;
  
namespace AspNetRoleBasedSecurity.Models
{
    public class ApplicationUser : IdentityUser
    {
        [Required]
        public string FirstName { get; set; }
   
        [Required]
        public string LastName { get; set; }
   
        [Required]
        public string Email { get; set; }
    }
    
  
    public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
    {
        public ApplicationDbContext()
            : base("DefaultConnection")
        {
            
        }
    }
  
  
    public class IdentityManager
    {
        public bool RoleExists(string name)
        {
            var rm = new RoleManager<IdentityRole>(
                new RoleStore<IdentityRole>(new ApplicationDbContext()));
            return rm.RoleExists(name);
        }
  
  
        public bool CreateRole(string name)
        {
            var rm = new RoleManager<IdentityRole>(
                new RoleStore<IdentityRole>(new ApplicationDbContext()));
            var idResult = rm.Create(new IdentityRole(name));
            return idResult.Succeeded;
        }
  
  
        public bool CreateUser(ApplicationUser user, string password)
        {
            var um = new UserManager<ApplicationUser>(
                new UserStore<ApplicationUser>(new ApplicationDbContext()));
            var idResult = um.Create(user, password);
            return idResult.Succeeded;
        }
  
  
        public bool AddUserToRole(string userId, string roleName)
        {
            var um = new UserManager<ApplicationUser>(
                new UserStore<ApplicationUser>(new ApplicationDbContext()));
            var idResult = um.AddToRole(userId, roleName);
            return idResult.Succeeded;
        }
  
  
        public void ClearUserRoles(string userId)
        {
            var um = new UserManager<ApplicationUser>(
                new UserStore<ApplicationUser>(new ApplicationDbContext()));
            var user = um.FindById(userId);
            var currentRoles = new List<IdentityUserRole>();
            currentRoles.AddRange(user.Roles);
            foreach(var role in currentRoles)
            {
                um.RemoveFromRole(userId, role.Role.Name);
            }
        }
    }
}

Yeah, I know. There is room for some refactoring here.We’ll ignore that for now. In the above, we have extended ApplicationUser to include our new required properties, and added the IdentityManager class, which includes the methods required to create new users, and add/remove users from available roles.

We have also decorated our new ApplicationUser properties with the [Required] data annotation, which will be reflected both in our database (nulls will not be allowed) and our Model Validation for our views.

SIDE NOTE, REDUX: I am utilizing the methods available directly within the Microsoft.AspNet.Identity and Microsoft.AspNet.Identity.EntityFramework namespaces. I am content to let the ASP.NET team invent and provide the best practices for managing application security. Therefore, in the context of this application, I am not inventing my own. I strongly recommend you do as well. It is easy to spot ways to manage some of the Account/Identity stuff (including data persistence) which appear more direct or easier. I concluded that the team thought all this through more effectively than I can. Therefore, while we are creating an authorization management structure here, we are doing so using the core implementation provided by people who know better than we do.

Extending Account Management ViewModels

Now that we have expanded upon our basic Identity Models, we need to do the same with our Account ViewModels. ViewModels essentially represent a data exchange mechanism between our views and Controllers. Our goal here is to provide our Account management views with precisely the information required to perform the task at hand, and no more.

Also of note is that for the purpose of our presentation layer, I am not pushing User or Role id’s out onto the page or the backing html. Instead I am relying on the unique nature of the Username and Role names to look up the proper Id value server-side.

Currently, before we do anything, our AccountVeiwModels.cs file looks like this:

The AccountViewModels.cs File Before Modification:
using System.ComponentModel.DataAnnotations;
namespace AspNetRoleBasedSecurity.Models
{
    public class ManageUserViewModel
    {
        [Required]
        [DataType(DataType.Password)]
        [Display(Name = "Current password")]
        public string OldPassword { get; set; }
  
        [Required]
        [StringLength(100, ErrorMessage = 
            "The {0} must be at least {2} characters long.", MinimumLength = 6)]
        [DataType(DataType.Password)]
        [Display(Name = "New password")]
        public string NewPassword { get; set; }
  
        [DataType(DataType.Password)]
        [Display(Name = "Confirm new password")]
        [Compare("NewPassword", ErrorMessage = 
            "The new password and confirmation password do not match.")]
        public string ConfirmPassword { get; set; }
    }
  
    public class LoginViewModel
    {
        [Required]
        [Display(Name = "User name")]
        public string UserName { get; set; }
  
        [Required]
        [DataType(DataType.Password)]
        [Display(Name = "Password")]
        public string Password { get; set; }
  
        [Display(Name = "Remember me?")]
        public bool RememberMe { get; set; }
    }
  
    public class RegisterViewModel
    {
        [Required]
        [Display(Name = "User name")]
        public string UserName { get; set; }
  
        [Required]
        [StringLength(100, ErrorMessage = 
            "The {0} must be at least {2} characters long.", MinimumLength = 6)]
        [DataType(DataType.Password)]
        [Display(Name = "Password")]
        public string Password { get; set; }
  
        [DataType(DataType.Password)]
        [Display(Name = "Confirm password")]
        [Compare("Password", ErrorMessage = 
            "The password and confirmation password do not match.")]
        public string ConfirmPassword { get; set; }
    }
}

We are going to expand on this significantly. In fact, it may seem, redundantly. For, with a few minor differences, it might appear that one or two of the ViewModels in the following code are near-duplicates, and possible candidates for a refactoring into a single class. However, I decided that the purpose of the ViewModel is to represent the specific data required by a specific view. While in some cases these appear to be the same, that may change. I concluded that it is better, so far as Views and ViewModels go, to have some potential duplication, but preserve the ability of each view to evolve independently should the need arise, without having to fuss with the impact on other views dependent on the same ViewModel.

Modify the code above as follows (or simply replace it with the following). We’ll take a closer look at the functionality in a moment, when we get to the Controller implementation:

Modified Code for AccountViewModels.cs File:
using System.ComponentModel.DataAnnotations;
  
// New namespace imports:
using Microsoft.AspNet.Identity.EntityFramework;
using System.Collections.Generic;
  
namespace AspNetRoleBasedSecurity.Models
{
    public class ManageUserViewModel
    {
        [Required]
        [DataType(DataType.Password)]
        [Display(Name = "Current password")]
        public string OldPassword { get; set; }
  
        [Required]
        [StringLength(100, ErrorMessage = 
            "The {0} must be at least {2} characters long.", MinimumLength = 6)]
        [DataType(DataType.Password)]
        [Display(Name = "New password")]
        public string NewPassword { get; set; }
  
        [DataType(DataType.Password)]
        [Display(Name = "Confirm new password")]
        [Compare("NewPassword", ErrorMessage = 
            "The new password and confirmation password do not match.")]
        public string ConfirmPassword { get; set; }
    }
  
  
    public class LoginViewModel
    {
        [Required]
        [Display(Name = "User name")]
        public string UserName { get; set; }
  
        [Required]
        [DataType(DataType.Password)]
        [Display(Name = "Password")]
        public string Password { get; set; }
  
        [Display(Name = "Remember me?")]
        public bool RememberMe { get; set; }
    }
  
  
    public class RegisterViewModel
    {
        [Required]
        [Display(Name = "User name")]
        public string UserName { get; set; }
  
        [Required]
        [StringLength(100, ErrorMessage = 
            "The {0} must be at least {2} characters long.", MinimumLength = 6)]
        [DataType(DataType.Password)]
        [Display(Name = "Password")]
        public string Password { get; set; }
  
        [DataType(DataType.Password)]
        [Display(Name = "Confirm password")]
        [Compare("Password", ErrorMessage = 
            "The password and confirmation password do not match.")]
        public string ConfirmPassword { get; set; }
  
        // New Fields added to extend Application User class:
  
        [Required]
        [Display(Name = "First Name")]
        public string FirstName { get; set; }
  
        [Required]
        [Display(Name = "Last Name")]
        public string LastName { get; set; }
  
        [Required]
        public string Email { get; set; }
  
        // Return a pre-poulated instance of AppliationUser:
        public ApplicationUser GetUser()
        {
            var user = new ApplicationUser()
            {
                UserName = this.UserName,
                FirstName = this.FirstName,
                LastName = this.LastName,
                Email = this.Email,
            };
            return user;
        }
    }
  
  
    public class EditUserViewModel
    {
        public EditUserViewModel() { }
  
        // Allow Initialization with an instance of ApplicationUser:
        public EditUserViewModel(ApplicationUser user)
        {
            this.UserName = user.UserName;
            this.FirstName = user.FirstName;
            this.LastName = user.LastName;
            this.Email = user.Email;
        }
  
        [Required]
        [Display(Name = "User Name")]
        public string UserName { get; set; }
  
        [Required]
        [Display(Name = "First Name")]
        public string FirstName { get; set; }
  
        [Required]
        [Display(Name = "Last Name")]
        public string LastName { get; set; }
  
        [Required]
        public string Email { get; set; }
    }
  
  
    public class SelectUserRolesViewModel
    {
        public SelectUserRolesViewModel() 
        {
            this.Roles = new List<SelectRoleEditorViewModel>();
        }
  
  
        // Enable initialization with an instance of ApplicationUser:
        public SelectUserRolesViewModel(ApplicationUser user) : this()
        {
            this.UserName = user.UserName;
            this.FirstName = user.FirstName;
            this.LastName = user.LastName;
  
            var Db = new ApplicationDbContext();
  
            // Add all available roles to the list of EditorViewModels:
            var allRoles = Db.Roles;
            foreach(var role in allRoles)
            {
                // An EditorViewModel will be used by Editor Template:
                var rvm = new SelectRoleEditorViewModel(role);
                this.Roles.Add(rvm);
            }
  
            // Set the Selected property to true for those roles for 
            // which the current user is a member:
            foreach(var userRole in user.Roles)
            {
                var checkUserRole = 
                    this.Roles.Find(r => r.RoleName == userRole.Role.Name);
                checkUserRole.Selected = true;
            }
        }
  
        public string UserName { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public List<SelectRoleEditorViewModel> Roles { get; set; }
    }
  
    // Used to display a single role with a checkbox, within a list structure:
    public class SelectRoleEditorViewModel
    {
        public SelectRoleEditorViewModel() {}
        public SelectRoleEditorViewModel(IdentityRole role)
        {
            this.RoleName = role.Name;
        }
  
        public bool Selected { get; set; }
  
        [Required]
        public string RoleName { get; set;}
    }
}

Extending the Account Controller

Now that we have our Models and ViewModels mostly in place, let’s look at how it all comes together in the Controller. Our current AccountController defines Controller Actions for the following:

  • Register (Essentially creates a new user)
  • Manage (Essentially allows the user to change their password)
  • Login
  • Log Off

Also, the above methods are focused upon allowing anonymous users to self-register, and create their own user account.

We are not planning to allow self-registration, and our requirements establish that user accounts are to be created by an administrator. Also, we have extended our ApplicationUser model to include some additional properties. From a functional perspective, we would like to se the following behavior implemented:

  • View a list of user accounts (Index), with links to various relevant functionality (Edit, Roles, Etc.)
  • Create a new User (We will co-opt the “Register” method for this, but we will extend it significantly).
  • Edit a User (Administrators need to be able to edit user accounts, assign roles, and such)
  • Delete a User (We want to be able to remove User accounts (or at least render them active or inactive)
  • Assign Roles to a User
  • Login
  • Log Off

Before we proceed, understand that there are countless possible major and minor variations we could consider for the above. I chose an application model which was simple, and for our purposes, rather arbitrary. For example, it could be your application requirements allow for self-registration of anonymous users into a default Role of some sort. The model I represent here is by purpose rather limited in scope, as we are trying to see concepts without getting too distracted by a complex implementation. I leave to you to expand from here into more complex (and more useful to you) application models.

In keeping with the above, I have added the [Authorize(Roles = "Admin")] attribute to all of the administrative methods, with the assumption that our administrative role will be called (wait for it . . .) “Admin.” More on this later too.

Where was I?

Oh, yeah. So, looking at the functional needs in the list above, I am going to modify my AccountController to incorporate the above items. As mentioned parenthetically, I am simply going to co-opt the Register controller method and use it for what should probably be named Create (out of laziness at this point!).

Modifying The AccountController Register Method (Create User)

First, we will look at modifying our existing Register method(s) to accommodate our new ApplicationUser properties. We want to be able to create a new ApplicationUser in our View, and then persist the new record in our database.

Modified Register Method:
[Authorize(Roles = "Admin")]
public ActionResult Register()
{
    return View();
}
  
  
[HttpPost]
[Authorize(Roles = "Admin")]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
    if (ModelState.IsValid)
    {
        var user = model.GetUser();
        var result = await UserManager.CreateAsync(user, model.Password);
        if (result.Succeeded)
        {
            return RedirectToAction("Index", "Account");
        }
    }
  
    // If we got this far, something failed, redisplay form
    return View(model);
}

In the above, we have not changed much except using the handy GetUser() method defined on our RegisterViewModel to retrieve an instance of ApplicationUser, populated and ready to persist in our database. Also, we are redirecting to a new Index method we will define momentarily on our AccountController.

Adding The AccountController Index Method (View List of Users)

Previously, the AccountController did not have an Index method. We need a way for our Administrators to view a list of users of our application, and access the functionality to edit, assign roles, and delete. Once again, accessing the user account data is an admin function, so we have added the [Authorize] attribute as well.

The New Index Method:
[Authorize(Roles = "Admin")]
public ActionResult Index()
{
    var Db = new ApplicationDbContext();
    var users = Db.Users;
    var model = new List<EditUserViewModel>();
    foreach(var user in users)
    {
        var u = new EditUserViewModel(user);
        model.Add(u);
    }
    return View(model);
}

Our Index method uses a List<EditUserViewModel> for now, as it contains all the information needed for display in our list. Contrary to what I said above, I have re-used a ViewModel here. I should probably fix that, but you can make your own decision on this point.

Notice that instead of performing the tedious mapping of properties from ApplicationUser instance to each EditUserViewModel within this method, I simply pass the ApplicationUser instance into the overridden constructor on EditUserViewModel. Our Index.cshtml View will expect a List<EditUserViewModel> as the model for display.

Adding The AccountController Edit Method

We have added an Edit method to facilitate administrative updating of User account data. There are some details to pay attention to in the Edit method implementation, at least in my version. First, while the method still accepts a parameter named id, what we will actually be passing to the method when a request is routed here will be a UserName. Why? I decided to follow the lead of the ASP.NET team on this. They are not passing User Id’s (which are GUID’s) out into the public HTML, so neither will I.

Also, by design and constraint, the UserName is unique in our database, and will already be a semi-public piece of information. Just something to keep in mind – the id parameter for public Account Action methods will be the User (or, as the case may be, Role) name, and not an integer. Lastly, I didn’t want to add a whole new route just to rename a single route parameter which is serving essentially the same purpose as an int id.

That said, the following is the new Edit method, which will be used when an Administrator wishes to update user information:

The New Edit Method:
[Authorize(Roles = "Admin")]
public ActionResult Edit(string id, ManageMessageId? Message = null)
{
    var Db = new ApplicationDbContext();
    var user = Db.Users.First(u => u.UserName == id);
    var model = new EditUserViewModel(user);
    ViewBag.MessageId = Message;
    return View(model);
}
[HttpPost]
[Authorize(Roles = "Admin")]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit(EditUserViewModel model)
{
    if (ModelState.IsValid)
    {
        var Db = new ApplicationDbContext();
        var user = Db.Users.First(u => u.UserName == model.UserName);
        // Update the user data:
        user.FirstName = model.FirstName;
        user.LastName = model.LastName;
        user.Email = model.Email;
        Db.Entry(user).State = System.Data.Entity.EntityState.Modified;
        await Db.SaveChangesAsync();
        return RedirectToAction("Index");
    }
    // If we got this far, something failed, redisplay form
    return View(model);
}

In the above, we use Linq to grab a reference to the specific user based on the UserName passed in as the id parameter in the first (GET) Edit method. We then populate an instance of EditUserViewModel by passing the ApplicationUser Instance to the constructor, and pass the ViewModel on to the Edit.cshtml View.

When the View returns our updated model to the second (POST) Edit method, we do much the same in reverse. We retrieve the User record from the database, then update with the model data, and save changes.

In our View, it will be important to remember that we cannot allow editing of the UserName property itself (at least, not under our current model, which considers the UserName to be an inviolate identifier).

Adding the AccountController Delete Method

We are adding a Delete (GET) method and a DeleteConfirmed (POST) method to the AccountController class. In my implementation, this method will actually delete the selected user from the database. You may decide instead to flag the database record as deleted, or implement some other method of managing unwanted user records.

You might also forego a delete method, and instead add a Boolean Inactive property to the ApplicationUser class, and manage Active/Inactive status through the Edit method discussed previously. Again, there are many permutations to the design model here. I went with the simplest for the sake of clarity.

The Delete method implementation here is straightforward so long as we remember, once again, that the id parameter passed to each of the two related methods below is actually a UserName.

The New Delete Methods:
[Authorize(Roles = "Admin")]
public ActionResult Delete(string id = null)
{
    var Db = new ApplicationDbContext();
    var user = Db.Users.First(u => u.UserName == id);
    var model = new EditUserViewModel(user);
    if (user == null)
    {
        return HttpNotFound();
    }
    return View(model);
}
  
  
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
[Authorize(Roles = "Admin")]
public ActionResult DeleteConfirmed(string id)
{
    var Db = new ApplicationDbContext();
    var user = Db.Users.First(u => u.UserName == id);
    Db.Users.Remove(user);
    Db.SaveChanges();
    return RedirectToAction("Index");
}

As we can see, the DeleteConfirmed method is decorated with a HttpPost attribute, and an ActionName attribute “Delete” which means POST requests routed to Delete will be routed here. Both methods use the UserName passed as the id parameter to look up the appropriate ApplicationUser in the database.

Once again, in contrast to what I said earlier, I re-used the EditUserViewModel to pass to the Delete.cshtml View.

Adding the UserRoles Method to the AccountController

Lastly, we add the UserRoles method pair. This is where we manage assignment of user accounts to various application roles.

The implementation here looks relatively simple, and pretty similar to the other controller methods we have examined so far. However, under the hood in the SelectUserRolesViewModel and in the IdentityManager class, there is a lot going on. First, the code:

The New UserRoles Method(s):
[Authorize(Roles = "Admin")]
public ActionResult UserRoles(string id)
{
    var Db = new ApplicationDbContext();
    var user = Db.Users.First(u => u.UserName == id);
    var model = new SelectUserRolesViewModel(user);
    return View(model);
}
  
  
[HttpPost]
[Authorize(Roles = "Admin")]
[ValidateAntiForgeryToken]
public ActionResult UserRoles(SelectUserRolesViewModel model)
{
    if(ModelState.IsValid)
    {
        var idManager = new IdentityManager();
        var Db = new ApplicationDbContext();
        var user = Db.Users.First(u => u.UserName == model.UserName);
        idManager.ClearUserRoles(user.Id);
        foreach (var role in model.Roles)
        {
            if (role.Selected)
            {
                idManager.AddUserToRole(user.Id, role.RoleName);
            }
        }
        return RedirectToAction("index");
    }
    return View();
}

As we can see above, and incoming GET request routed to the UserRoles method is handled similarly to those in previous methods. The UserName passed as the id parameter is used to retrieve the User record from the database, and then an instance of SelectUserRolesViewModel is initialized, passing the ApplicationUser instance to the constructor.

Here is where things get interesting. Let’s take another look at the code for our SelectUserRolesViewModel from the AccountViewModels.cs file:

Code for SelectUserRolesViewModel – Revisited:
public class SelectUserRolesViewModel
{
    public SelectUserRolesViewModel() 
    {
        this.Roles = new List<SelectRoleEditorViewModel>();
    }
  
  
    // Enable initialization with an instance of ApplicationUser:
    public SelectUserRolesViewModel(ApplicationUser user) : this()
    {
        this.UserName = user.UserName;
        this.FirstName = user.FirstName;
        this.LastName = user.LastName;
  
        var Db = new ApplicationDbContext();
  
        // Add all available roles to the list of EditorViewModels:
        var allRoles = Db.Roles;
        foreach(var role in allRoles)
        {
            // An EditorViewModel will be used by Editor Template:
            var rvm = new SelectRoleEditorViewModel(role);
            this.Roles.Add(rvm);
        }
  
        // Set the Selected property to true for those roles for 
        // which the current user is a member:
        foreach(var userRole in user.Roles)
        {
            var checkUserRole = 
                this.Roles.Find(r => r.RoleName == userRole.Role.Name);
            checkUserRole.Selected = true;
        }
    }
  
    public string UserName { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public List<SelectRoleEditorViewModel> Roles { get; set; }
}

During initialization, we are populating a List<SelectRoleEditorViewModel>() with all the roles available in the application. First, this is a prime candidate for refactoring, as I am performing  data access within the object constructor (a general no-no). Second, SelectRoleEditorViewModel? What?

In my current implementation, we will see that the SelectUserRolesViewModel is passed to the UserRoles.cshtml view. We want to display the basic user details (so we know which user we are assigning roles to – always important to know), as well as a list of all available Roles. I have decided to facilitate role assignment using checkboxes, whereby roles are assigned to the user by checking one or more (or none!) checkboxes.

This is where the EditorViewModel comes in. We are going to use a common technique for adding checkboxes to a table and allowing the user to select from the list of items.

Let’s revisit the code for SelectRoleEditorViewModel, which we defined in our AccountViewModels.cs file.

The SelectRoleEditorViewModel represents an individual role, and as we can see from the following code, includes a Boolean field used to indicate the Selected status for that role:

Code for SelectRoleEditorViewModel, Revisited:
public class SelectRoleEditorViewModel
{
    public SelectRoleEditorViewModel() { }
    public SelectRoleEditorViewModel(IdentityRole role)
    {
        this.RoleName = role.Name;
    }
  
    public bool Selected { get; set; }
  
    [Required]
    public string RoleName { get; set; }
}

This EditorViewModel will be used by a special View called an EditorTemplate, which we will look at shortly. For now, bear in mind that the SelectUserRolesViewModel contains a List of SelectRoleEditorViewModel objects (yes, the naming of these could be better and posed some challenges – I am open to suggestion here! For the moment, I try to think of them as “SelectUserRoles-ViewModel” and “SelectRole-EditorViewModel” if that helps).

That covers the modified or new items in our AccountController. Now let’s look at our Views.

Basic Views for Role-Based Identity Management

We already have a few of the views we will need, we just need to modify them a bit. In addition, we need to add a few new ones. We’ll start by modifying our existing views to suit our needs, beginning with the Register.cshmtl View.

Modifying the Register.cshtml View File

The Register view as it currently sits was designed to allow user self-registration. As we have co-opted the Register method on the AccountController for restricted administrative use, so we will co-opt the Register.cshtml View for our purposes.

Essentially, all we need to do is add some additional HTML and Razor-Syntax code to the file to accommodate the new properties we added to our ApplicationUser class:

Modifying the Register.cshtml File:
@model AspNetRoleBasedSecurity.Models.RegisterViewModel
@{
    ViewBag.Title = "Register";
}
  
<h2>@ViewBag.Title.</h2>
  
@using (Html.BeginForm("Register", "Account", FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
{
    @Html.AntiForgeryToken()
    <h4>Create a new account.</h4>
    <hr />
    @Html.ValidationSummary()
    <div class="form-group">
        @Html.LabelFor(m => m.UserName, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.UserName, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.Password, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.PasswordFor(m => m.Password, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.ConfirmPassword, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.PasswordFor(m => m.ConfirmPassword, new { @class = "form-control" })
        </div>
    </div>
  
    // Add the LastName, FirstName, and Email Properties:
    <div class="form-group">
        @Html.LabelFor(m => m.LastName, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.LastName, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.FirstName, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.FirstName, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.Email, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.Email, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input type="submit" class="btn btn-default" value="Register" />
        </div>
    </div>
}

Next, we will create our edit view. To do this, we can right-click on the Edit Action method declaration in AccountController and let VS do the work for us:

Create View for the Edit Method:

add-view-edit-method

We next see the Add View Dialog. Choose the Edit template from the Template drop-down, and select the EditUserViewModel class from the Model Class drop-down. We already have a data context, so leave that blank.

Add View Dialog:

add-view-dialog

Repeat the process above for the Delete and Index methods. Choose the appropriate template for each (use the List template for the Index View, as we want to display a list of User Accounts), and use EditUserViewModel as the Model Class for both.

Fine-Tuning the Index View

We need to make a few minor changes to our index view.

Notice near the bottom, where the template has provided handy links for Edit, Details, and Delete. We will change the “Details” link to instead point to our UserRoles Action method. Also, we need to replace the commented out route parameters such that the Username is passed as the id Parameter.

Lastly, up near the top of the file the is some razor code for an Action link to create a new user. Replace the Action method parameter “Create” with our co-opted “Register” method.

After our modifications, the final Index.cshtml file should look like this:

Modified Index.cshtml File:
@model IEnumerable<AspNetRoleBasedSecurity.Models.EditUserViewModel>
  
@{
    ViewBag.Title = "Index";
}
  
<h2>Index</h2>
  
<p>
    @Html.ActionLink("Create New", "Register") 
</p>
<table class="table">
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.UserName)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.FirstName)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.LastName)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Email)
        </th>
        <th></th>
    </tr>
  
@foreach (var item in Model) {
    <tr>
        <td>
            @Html.DisplayFor(modelItem => item.UserName)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.FirstName)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.LastName)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Email)
        </td>
        <td>
            @Html.ActionLink("Edit", "Edit", new { id = item.UserName }) |
            @Html.ActionLink("Roles", "UserRoles", new { id = item.UserName }) |
            @Html.ActionLink("Delete", "Delete", new { id = item.UserName })
        </td>
    </tr>
}
  
</table>

Now we can get back to that whole User Roles issue.

Create the UserRoles.cshtml View

We can use the VS Add View method to create our UserRoles.cshtml View as we did with the previous views. However, we will be doing most of our work from scratch on this one. Right-Click on the UserRoles method of AccountController and select Add View. This time, however, choose the Empty template option from the Template drop-down, and choose SelectUserRolesViewModel from the Model Class drop-down.

You should now have a mostly empty UserRoles.cshtml file the looks like this:

The Empty UserRoles.cshtml File:
@model AspNetRoleBasedSecurity.Models.SelectUserRolesViewModel
  
@{
    ViewBag.Title = "UserRoles";
}
  
<h2>UserRoles</h2>

From here, we will add our code manually. We want to display the basic user information, followed by a list of the available roles to which the user can be assigned (or removed). We want the list of roles to feature checkboxes as the selection mechanism.

To accomplish the above, we will add our Razor code as follows:

Added Code to the UserRoles.cshtml File:
@model AspNetRoleBasedSecurity.Models.SelectUserRolesViewModel
  
@{
    ViewBag.Title = "User Roles";
}
  
<h2>Roles for user @Html.DisplayFor(model => model.UserName)</h2>
<hr />
  
@using (Html.BeginForm("UserRoles", "Account", FormMethod.Post, new { encType = "multipart/form-data", name = "myform" }))
{
    @Html.AntiForgeryToken()
  
    <div class="form-horizontal">
        @Html.ValidationSummary(true)
        <div class="form-group">
            <div class="col-md-10">
                @Html.HiddenFor(model => model.UserName)
            </div>
        </div>
  
        <h4>Select Role Assignments</h4>
        <br />
        <hr />
  
        <table>
            <tr>
                <th>
                    Select
                </th>
                <th>
                    Role
                </th>
            </tr>
            @Html.EditorFor(model => model.Roles)
        </table>
        <br />
        <hr />
  
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Save" class="btn btn-default" />
            </div>
        </div>
    </div>
}

In the above, we have set up a display header featuring the UserName, and created an HTML Table with header elements for Select and Role. The Select column will contain the checkboxes for each role, and the Role column will, obviously, display the Role names.

Below the table header set up, notice the line:

@Html.EditorFor(model => model.Roles)

This is where we return to that EditorTemplate concept. An Editor template is a Shared View, and needs to be located in the Views => Shared => EditorTemplates folder in your project. You may need to create the folder yourself.

Create the SelectRoleEditorViewModel Editor Template by right-clicking on your new EditorTemplates folder and selecting Add View. Use the Empty template again, and name the view SelectRoleEditorViewModel (this is important). Choose SelectRoleEditorViewModel from the Model Classes drop-down. When you are done you should have a .cshtml file that looks like this:

The Empty SelectRoleEditorViewModel Editor Template File:
@model AspNetRoleBasedSecurity.Models.SelectRoleEditorViewModel
  
@{
    ViewBag.Title = "SelectRoleEditorViewModel";
}
  
<h2>SelectRoleEditorViewModel</h2>

From here, we will add a few lines, so that our file looks like this:

Modified SelectRoleEditorViewModel Editor Template File:
@model AspNetRoleBasedSecurity.Models.SelectRoleEditorViewModel
@Html.HiddenFor(model => model.RoleName)
<tr>
    <td style="text-align:center">
        @Html.CheckBoxFor(model => model.Selected)
    </td>
    <td>
        @Html.DisplayFor(model => model.RoleName)
    </td>
</tr>

Now we have an editor template for our SelectRoleEditorViewModel. The code in our UserRoles.cshtml View will use this template to render our list of roles, including the checkboxes. Selections made in the checkboxes will be reflected in our model and returned, with the Role name, to the controller when the form is submitted.

Adding the Admin Tab to the Main Site Page

We are almost there. However, none of these new views and functionality do us much good if we can’t get to it from within our application. We need to add an Admin tab to our main site page, and also remove the ability for anonymous users to access the registration link included on the site by default.

To do this, we need to modify the _Layout.cshtml file in the Views => Shared folder.

Modified _Layout.cshtml File
<div class="navbar-collapse collapse">
    <ul class="nav navbar-nav">
        <li>@Html.ActionLink("Home", "Index", "Home")</li>
        <li>@Html.ActionLink("About", "About", "Home")</li>
        <li>@Html.ActionLink("Contact", "Contact", "Home")</li>
        <li>@Html.ActionLink("Admin", "Index", "Account")</li>
    </ul>
    @Html.Partial("_LoginPartial")
</div>

The code above is from just about the middle of the _Layout.cshtml View file. Add the highlighted line to create a tab link pointing to the Index method of our AccountController.

Remove the Register Link from the _LoginPartial.cshtml View

Last, we want to remove the link to the Register method from the main site layout. This link is found on the _LoginPartial.cshtml file, again located in the Views => Shared folder. At the bottom of this file, remove the code line highlighted below:

Remove the Register Link from _LoginPartial.cshtml:
else
{
    <ul class="nav navbar-nav navbar-right">
        <li>@Html.ActionLink("Register", "Register", "Account", routeValues: null, htmlAttributes: new { id = "registerLink" })</li>
        <li>@Html.ActionLink("Log in", "Login", "Account", routeValues: null, htmlAttributes: new { id = "loginLink" })</li>
    </ul>
}

Now all our views should be ready.

Set Up and Run Entity Framework Migrations

Now that we have most of the pieces in place, it’s time to set up Code-First Migrations with Entity Framework. Also, because we are ostensibly building an application in which only users in an administrative role can create or edit users, we need to seed our application with an initial Admin user. Further, since we don’t plan to allow creation or modification of roles, we need to seed the database with the roles we expect to use in our application, since we can’t create them from within the application itself.

We covered EF Code-First Migrations fairly thoroughly in the previous article, so I am going to skim through it this time.

First, enable migrations by typing the following in the Package Manager Console:

Enable EF Migrations in Your Project:
PM> Enable-Migrations –EnableAutomaticMigrations

Now, open the Migrations => Configuration.cs file and add the following code (tune it up to suit your specifics. You probably don’t want to add MY information as your admin user. Also note, whatever password you provide to start with must conform to the constraints of the application, which appears to require at least one capital letter, and at least one number:

Modify the EF Migrations Configuration File with Seed Data:
using AspNetRoleBasedSecurity.Models;
using System.Data.Entity.Migrations;
namespace AspNetRoleBasedSecurity.Migrations
{
    using System;
    using System.Data.Entity;
    using System.Data.Entity.Migrations;
    using System.Linq;
  
    internal sealed class Configuration : DbMigrationsConfiguration<ApplicationDbContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = true;
        }
  
  
        protected override void Seed(ApplicationDbContext context)
        {
            this.AddUserAndRoles();
        }
  
  
        bool AddUserAndRoles()
        {
            bool success = false;
  
            var idManager = new IdentityManager();
            success = idManager.CreateRole("Admin");
            if (!success == true) return success;
  
            success = idManager.CreateRole("CanEdit");
            if (!success == true) return success;
  
            success = idManager.CreateRole("User");
            if (!success) return success;
  
  
            var newUser = new ApplicationUser()
            {
                UserName = "jatten",
                FirstName = "John",
                LastName = "Atten",
                Email = "jatten@typecastexception.com"
            };
  
            // Be careful here - you  will need to use a password which will 
            // be valid under the password rules for the application, 
            // or the process will abort:
            success = idManager.CreateUser(newUser, "Password1");
            if (!success) return success;
  
            success = idManager.AddUserToRole(newUser.Id, "Admin");
            if (!success) return success;
  
            success = idManager.AddUserToRole(newUser.Id, "CanEdit");
            if (!success) return success;
  
            success = idManager.AddUserToRole(newUser.Id, "User");
            if (!success) return success;
  
            return success;
        }
    }
}

Once that is done, run the following command from the Package Manager Console:

Add the Initial EF Migration
Add-Migration Init

Then create the database by running the Update-Database command:

Update Database Command:
Update-Database

If all went well, your database should be created as a SQL Server (local) database in the App_Data folder. If you want to point to a different Database Server, review the previous article where we discuss pointing the default connection string to a different server.

You can check to see if your database was created properly by opening the Server Explorer window in Visual Studio. Or, of course, you could simply run your application, and see what happens!

Use [Authorize] Attribute to Control Access

From this point, we can regulate access to different application functionality using the [Authorize] Attribute. We have already seen examples of this on the methods in our AccountController, where access to everything except the Login method is restricted to users of the Admin role.

Use [Authorize] Attribute to Control Access to Functionality:
[AllowAnonymous]
public MyPublicMethod()
{
    // Code
}
  
  
[Authorize(Role = "Admin, CanEdit, User")]
public MyPrettyAccessibleMethod()
{
    // Code
}
  
  
[Authorize(Role = "Admin, CanEdit")]
public MyMoreRestrictiveMethod()
{
    // Code
}
  
  
[Authorize(Role = "Admin")]
public MyVeryRestrictedMethod()
{
    // Code
}

In the code above, we see progressively more restricted method access based on Role access defined using the [Authorize] attribute.

At the moment, our role definitions are not “tiered” in a manner by which a higher-level role inherits the permissions associated with a more restricted role. For example, if a method is decorated with an [Authorize] attribute granting access to members of the User Role, it is important to note that ONLY members of that role will be able to access the method. Role access must be explicitly and specifically granted for each role under this scenario.

Role Permissions are not Inherited:
// Admins can't access this method:
[Authorize(Role = "Users")]
public SomeMethod()
{
    // Code
}
// Admins AND Users can access this method:
[Authorize(Role = "Users", Admins)]
public SomeMethod()
{
    // Code
}

Contrary to our experience with most operating system security, members of the Admin role do not automatically get all the permissions of the User role. We could probably achieve this, but such is beyond the scope of this article.

A Note About Roles and Role Naming

For our purposes here, I have used some rather generic role names, since we really don’t have any business cases to consider when using roles to manage application access.

The ASP.NET team recommends (and I agree) the it is best to use descriptive and limiting role definitions which describe, to an extent, the permissions associated with that role. For example, instead of a generic “Admin” role, one might create an “IdentityManager” role specific to Account and Identity management, and other such descriptive role names as make sense in the business context of your application.

Wrapping It Up

In this article we have created a very simple implementation of Role-Based Identity Management. As I mentioned at the beginning, the model used here, outside of any business context, is a little basic. I have attempted to create show some of the basics involved with using the ASP.NET MVC Identity system, extending it to include some custom properties, and modifying the use to suit a basic business case.

There is a lot to know about Web Application security, and in my mind, it is not the place to re-invent any wheels. In the application discussed here, we have re-jiggered the components of the ASP.NET Identity model, but we have used the core pieces as designed instead of inventing our own authorization mechanism.

Hopefully, this rather long article has been helpful, and I have not propagated any bad information. Please do let me know, either in the comments, or by email if you find any significant issues here. I will correct them promptly.

Additional Resources and Items of Interest

ASP.Net
ASP.NET Identity 2.0: Introduction to Working with Identity 2.0 and Web API 2.2
C#
I am a Programmer, and I can Complicate the #@$% out of a Ball Bearing
C#
Use Cross-Platform/OSS ExcelDataReader to Read Excel Files with No Dependencies on Office or ACE
  • Gawie Schneider

    HI John,

    Very nice article. I had to change a couple of things due to, assuming VS2015 & .net 4.6.1 where the Account & Management was split into different files instead of 1 file.
    However, the rest was pretty helpful.

    I started learning MVC, javascript, Ajax etc about a week ago, and I have to say thank you for your clear description of each class, method and action that was changed.

    I can now use this as a base going forward, which is very nice and helpful.

    Kudos to you good sir. :)

    I would like to stay in contact with you, as there are 1 or 2 more articles I would like for you to write about.


  • Muhammad Shan Arhad

    Assalam-o-Alaikum! John

    very Helpful Article for me, Awesome


    • John Atten

      John AttenJohn Atten

      Author Reply

      Muhammad – Wa-Alaikum-Salaam! Thanks for reading, and taking the time to comment :-)


  • Marius

    MariusMarius

    Author Reply

    Hi John

    Like many before me: awesome article!
    This was the first article I've worked through that explains everyting nicely.

    Thank you for your time on this article.

    With all the updates to the article up to now, I didn't get a single problem/error in getting the project to compile and run! Brilliant, and i now have a better understanding of Identity Roles.

    Regards


  • Maratto

    MarattoMaratto

    Author Reply

    Update – I got it working after some modifications in my project.


  • XIV-Admin

    XIV-AdminXIV-Admin

    Author Reply

    @Maratto –

    Again, I can take a look after work this evening. However, in the meantime, you might:

    A. Clone or download the source from my Github repo – the code there is known to work. The code in the articel, while I ahve done my best, may suffer from the copying process.

    B. Make absolutely sure the [b]SelectRoleEditorViewModel.cshtml[/b] template is located in a folder named [b]Views/Shared/EditorTemplates[/b], and that the file is named exactly the same as the model it represents (except the .cshtml extension, of course). This is critical for the "editor template" concept to work ("convention over configuration").

    If you can't get it by this evening, let me know, and we'll see what we can do.

    Cheers!


  • XIV-Admin

    XIV-AdminXIV-Admin

    Author Reply

    @Emanuele – I'm on my way to work, and won't be able to look into this in detail until this evening (US Central Standard Time).

    First thing to check through is if you have all the correct Namespace imports in your using statements. It looks like I lost one or two while copying the code from the source to the article. Specifically, I notice a reference to System.Linq is missing, which is where the "First" extension method lives.

    The "usings" for AccountController should include the following:

    using AspNetRoleBasedSecurity.Models;
    using Microsoft.AspNet.Identity;
    using Microsoft.AspNet.Identity.EntityFramework;
    using Microsoft.Owin.Security;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using System.Web;
    using System.Web.Mvc;

    Thanks for pointing this out, looks like I need to update the article!

    If this doesn't fix things, let me know and I will revisit this evening after work.

    -J


  • Maratto

    MarattoMaratto

    Author Reply

    Got another problem….

    The Editor template for the SelectRoleEditorViewModel type isn't working for me..

    Any chance that you could help me with the code for displaying it without using the editor template?


  • Emanuele

    EmanueleEmanuele

    Author Reply

    The Edit and Delete User method have this: var user = Db.Users.First(u => u.UserName == id);
    and Visual Studio 2013 is giving me an error on "First" as IDbSet does not contain the definition for "First".
    I did some research but I couldn't find an answer. Can you help?


  • XIV-Admin

    XIV-AdminXIV-Admin

    Author Reply

    Maratto –

    Yeah, that's where I was going to go when I investigated your source. I found the Migrations thing to be really cool, but not 100% intuitive that way.

    If you think of it, within limits, as "version control" for your database it helps. In other words, you can define a new migration (using Add-Migration <migrationName>, delete the previous database, then re-run whichever version of the migration you want reflected in your Db.

    Glad you got it working, and thanks for the update. Cheers! :-)


  • Maratto

    MarattoMaratto

    Author Reply

    Update: For some reason, after i re-migrated my Db contexts from scratch it all started working fine.
    Thought that the scaffolding works on the model code, i guess that the actual Db has something to do with that.


  • Maratto

    MarattoMaratto

    Author Reply

    Sure that will be much appreciated.

    Here's a Dropbox Link to all the files: https://www.dropbox.com/s/6tlq1isv06wcr7g/ProjectItfs.rar

    Didn't really know what code i should'v posted so i just got you everything.


  • jatten

    jattenjatten

    Author Reply

    @Maratto –

    Can you post some code in a gist, or push an example up to Github?

    I will try to take a look if you can . . .


  • Maratto

    MarattoMaratto

    Author Reply

    Hey, awesome tutorial!

    Got 1 problem though:
    I'm currently implementing your tutorial on a basic existing project with 2 Db contexts (an IdentityDbContext type which interacts with the ApplicationUser, and a DbContext type which interacts with the other entities) which are both migrated to a single SQL server Db.

    All went well until i tried to scaffold a view for the UserRoles method, and it gave me the next error if i try to scaffold it with the IdentityDbContext: "GenericArguments[0],'<ProjectName>.Models.ApplicationUser', on 'Microsoft.AspNet.IdentityFramework.IdentityDbContext[TUser]' Violates the constraint of type parameter 'TUser'.

    And the next Error if i try to scaffold the view with the DbContext: Entity type 'SelectRoleEditorViewModel' has no key defined. Define the key for this entity type…. (And it goes on saying the same thing about the model builder)

    Now, i looked everywhere trying to fix it with no success.
    I Annotated a [Key] as the RoleName in SelectRoleEditorViewModel and UserName in SelectUserRolesViewModel.. Tried to update-the data base and got about the same error from the console. I have getters and setters on all attributes and i pretty much find myself sitting still, scratching my head, and thinking what went wrong.

    Would be great if anyone will be able to throw in some knowledge gems…


  • jatten

    jattenjatten

    Author Reply

    @Mister Scruff –

    Ah. I see where you mean about the Edit method – the proper GET version of the method was present in the source, just missed including it in the article text. Oops. All fixed, thanks for pointing that stuff out!


  • jatten

    jattenjatten

    Author Reply

    @Mister Scruff –

    Ok, I see what I did now with the roles in Authorize attribute. Oops!


  • jatten

    jattenjatten

    Author Reply

    @Mister Scruff –

    Thanks so much for the feedback, and pointing out the errata! Gotta go fix it!

    Did I somehow miss the commas between roles in [Authorize]? Yikes! That's what I get when moving code around between editor and blog while still writing the demo!

    Cheers!


  • Mister Scruff

    As said before – I really enjoyed your effort :)

    One reason for the edit link to fail (as mentioned by others) is the Edit function specific targeting POST

    Then a 404 is returned

    Added this to AccountController

    [code]
    [Authorize(Roles = "IdentityManager, UserManager")]
    public ActionResult Edit(string id = null)
    {
    if (id == null)
    {
    return HttpNotFound();
    }
    var Db = new ApplicationDbContext();
    var user = Db.Users.First(u => u.UserName == id);
    EditUserViewModel model = new EditUserViewModel();
    model.FirstName = user.FirstName;
    model.LastName = user.LastName;
    model.Email = user.Email;
    model.Country = user.Country;
    model.Employee = user.Employee;
    model.UserName = user.UserName;
    return View(model);
    }
    [/code]

    For the Edit post to properly update the id parameter must be added

    [code]
    [HttpPost]
    [Authorize(Roles = "IdentityManager, UserManager")]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> Edit(string id = null, EditUserViewModel model = null)
    {
    if (id == null)
    {
    return HttpNotFound();
    }
    if (ModelState.IsValid)
    {
    var Db = new BlackCurryDbContext();
    var user = Db.Users.First(u => u.UserName == id);
    user.FirstName = model.FirstName;
    user.LastName = model.LastName;
    user.Email = model.Email;
    user.Country = model.Country;
    user.Employee = model.Employee;
    Db.Entry(user).State = System.Data.Entity.EntityState.Modified;
    await Db.SaveChangesAsync();
    return RedirectToAction("Index");
    }
    // If we got this far, something failed, redisplay form

    return View(model);
    }
    [/code]


  • Mister Scruff

    When having more than one role on an action the roles need to be in one string separated with comma.

    Like this
    [quote][Authorize(Roles = "Admin, Office, User")][/quote]


  • Mister Scruff

    Great article – thanks a lot for taking the time to do this. I have been struggling with asp.net membership and ended up writing my own. The identity management has gotten better and I agree – no need to reinvent anything. Only con could be the new identity is not open source yet.

    A loud applause from DK – you know the Capitol of Sweden :)


  • jatten

    jattenjatten

    Author Reply

    Josh – It appears so far that the namespace using Microsoft.AspNet.Identity.EntityFramework does not provide a direct way to do this. You may have to create your own data access (possibly even using old-fashioned ADO.NET and SQL) to do this separately.

    Between this and an issue another reader was having, there may be a new post coming about working around some of these things.

    Thanks for reading!


  • Josh

    JoshJosh

    Author Reply

    Great article! I had no idea you could do all this stuff with Identity.

    One thing I can't figure out for the life of me is how to get a list of all users in a particular role.

    Is there some call i'm missing?


  • jatten

    jattenjatten

    Author Reply

    Cool. It looks like you found an error in the code on the site – there should be no ActionLink to a "Create" method. I'll check it out. Yet another update. I'll pass on the usual editing royalty – check's in the mail! 😉

    Seriously, thanks for reading, and taking the time to comment. Really appreciate it.


  • Simon

    SimonSimon

    Author Reply

    I'd messed up the AccountControllers.cs somehow. Downloaded yours and it works fine. I have learned a lot from this so thanks again.


  • Simon

    SimonSimon

    Author Reply

    I'm not sure what's causing the issue with the edit function, been playing around with it for a while and everything says it should work but I'm getting "Server Error in '/' Application. The resource cannot be found." error when pressing edit in the index list. The create works fine, changed:

    in index.cshtml

    Change
    @Html.ActionLink("Create New", "Create")

    To
    @Html.ActionLink("Create New", "Register")

    Mystified about the edit feature:-)


  • jatten

    jattenjatten

    Author Reply

    If you download the source from Github, enable Nuget Package restore, and build, you should have no problems (but again, let me know if you do!).

    -J


  • jatten

    jattenjatten

    Author Reply

    Indeed, you are correct sir. Fixed. Was this what was causing your issue? If not, let me know. Seems to me like this would have been the culprit!

    Thanks for pointing that out, and definitely let me know if you are still having difficulty.

    -J


  • Simon

    SimonSimon

    Author Reply

    Hi John,

    Many thanks for this walkthrough. I found it invaluable as I'm new to MVC and this has been the most understandable article I have found on it and ASP.Net membership. It has helped me to gain a much better understanding of the subject and is much appreciated.

    Couple of things I can't get working are the edit and create users functions (create works but not by linking from the admin area) and the edit function returns "Server Error in '/' Application. The resource cannot be found." Can't get it to run directly either.

    Thanks once again for a great article which has been a lot of help:-)

    PS I think you replaced AspNetRoleBasedSecurity with IdentityManagementSimplified in the SelectRoleEditorViewModel


Leave a Reply to Simon
Cancel Reply