ASP.Net

ASP.NET Identity 2.0: Customizing Users and Roles


Image By Herry Lawford  | Some Rights Reserved

The ASP.NET Identity team released the Identity 2.0 framework RTM back in march. The new release contained significant additions to the functionality found in the original 1.0 release, and introduced some breaking changes as well.

In a previous post, we took a high-level look at how Identity 2.0 works by digging in to the Identity Samples application (which was, and still is, in beta, so things may continue to change). We also took a detailed look at implementing Email Account Confirmation and Two Factor Authentication, which represent a couple of the sexier features of the Identity 2.0 RTM release.

We have previously explored explored Extending Identity Accounts and Implementing Role-Based Authentication under Identity 1.0, as well as Extending and Modifying Roles. However, things have changed since then. If you are using Identity 1.0, those posts are still applicable, and you should refer to them now. If you are looking to dig in to Identity 2.0, keep reading!

Many of the customizations we previously needed to add on our own under Identity Version 1.0 have now been incorporated into the Version 2.0 RTM. Specifically, Role administration, and the assignment of users to one or more roles is implemented out of the box in the Identity Samples project. Extending the basic IdentityUser and Role classes is a more flexible proposition, but is more complex than previously.

In this post we will dig in and see what we need to do to extend the basic ApplicationUser and ApplicationRole types by adding some custom properties to each.

UPDATE: If you are looking to use integer keys instead of strings, see ASP.NET Identity 2.0 Extending Identity Models and Using Integer Keys Instead of Strings.

I’ve also created a ready-to-use Easily Extensible Identity 2.0 project template which goes a little farther than the examples in this article.

We will walk through things step-by step here, so you can follow along. However, I have created a Github repo containing the source for the finished project. If you run into trouble, I strongly recommend cloning the source to get a closer look.

Bear in mind, the code here is minimal, in that we don’t attempt to make any UI improvements, excess validations, or other things which may seem obvious from an application design standpoint. Instead, we try to keep things simple, so that we can focus on the topic at hand.

We’ll look at all that in a minute. First, we are going to start by installing the Identity Samples project.

Installing the Identity 2.0 Sample Project

The Identity team has created a sample project which can be installed into an empty ASP.NET Web Project. Note that as of this writing this is an alpha release, so some things may change. However, most of the basic functionality is implemented, and in fact the sample project is a strong starting point for using Identity 2.0 in your own site.

The Identity Samples project is available on Nuget. First, create an empty ASP.NET Web Project (It is important that you use the “Empty” template here, not MVC, not Webforms, EMPTY). Then open the Package Manager console and type:

PM> Install-Package Microsoft.AspNet.Identity.Samples -Pre

This may take a minute or two to run. When complete, your will see a basic ASP.NET MVC project in the VS Solution Explorer. Take a good look around the Identity 2.0 Samples project, and become familiar with what things are and where they are at.

Re-Engineering the Identity Samples Project Using Custom Types

The Identity Samples project provides a solid platform to use as the basis for incorporating the Identity 2.0 framework into a new ASP.NET MVC project. However, the project itself assumes you will be using the default string keys (which translates into string-based primary keys in our database), and also assumes you will be using the default types included with Identity Samples out of the box.

As we will see, Identity 2.0 provides a great deal of flexibility for implementing custom types derived from the interfaces and generic base classes that form the Identity framework core. However, building a project similar to Identity Samples from scratch would be a large undertaking. We’re going to take advantage of the work done by the Identity team in creating the ample project, and instead of starting from scratch, we will tweak this excellent foundation to implement our own customizations.

Core Identity 2.0 Objects are Generic

The basic types implemented by the Identity team in the Identity Samples project represent an abstraction layer on top of a more flexible set of base classes which use generic type arguments. For example, if we look at the IdentityModels.cs code file, we can see the ApplicationUser is derived from IdentityUser:

Application User as Implemented in the Identity Samples Project:
public class ApplicationUser : IdentityUser
{
    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(
        UserManager<ApplicationUser> manager)
    {
        var userIdentity = await manager.CreateIdentityAsync(
            this, DefaultAuthenticationTypes.ApplicationCookie);
        return userIdentity;
    }
}

IdentityUser in this case, belongs to the namespace Microsoft.AspNet.Identity.EntityFramework. We can use the VS “Go to Definition” function or a source decompiler (such as Just Decompile by Telerik or Reflector by Redgate) to take a closer look at IdentityUser:

The Identity User Class:
public class IdentityUser : 
    IdentityUser<string, IdentityUserLogin, IdentityUserRole, 
    IdentityUserClaim>, IUser, IUser<string>
{
    public IdentityUser()
    {
        this.Id = Guid.NewGuid().ToString();
    }
  
    public IdentityUser(string userName) : this()
    {
        this.UserName = userName;
    }
}

Here, we see that IdentityUser inherits from another base class, IdentityUser<TKey, TLogin, TRole, TClaim> as well as a couple interfaces. In this case the concrete IdentityUser passes specific type arguments to the generic base class. And this is where things begin to get interesting.

As it turns out, all of the basic types required to use Identity 2.0 begin life as generic base types, with similar type arguments allowing us to define custom implementations. Looking at the definitions for the core Identity components used to build up the Identity Samples project, we find the following classes, shown here in terms of the concrete type arguments used in the default Identity constructs:

Default Identity 2.0 Class Signatures with Default Type Arguments:
public class IdentityUserRole 
    : IdentityUserRole<string> {}
  
public class IdentityRole 
    : IdentityRole<string, IdentityUserRole> {}
  
public class IdentityUserClaim 
    : IdentityUserClaim<string> {}
  
public class IdentityUserLogin 
    : IdentityUserLogin<string> {}
  
public class IdentityUser 
    : IdentityUser<string, IdentityUserLogin, 
        IdentityUserRole, IdentityUserClaim>, IUser, IUser<string> {}
  
public class IdentityDbContext 
    : IdentityDbContext<IdentityUser, IdentityRole, string, 
        IdentityUserLogin, IdentityUserRole, IdentityUserClaim> {}
  
public class UserStore<TUser> 
    : UserStore<TUser, IdentityRole, string, IdentityUserLogin, 
        IdentityUserRole, IdentityUserClaim>, 
        IUserStore<TUser>, IUserStore<TUser, string>, IDisposable
    where TUser : IdentityUser {}
  
public class RoleStore<TRole> 
    : RoleStore<TRole, string, IdentityUserRole>, IQueryableRoleStore<TRole>, 
        IQueryableRoleStore<TRole, string>, IRoleStore<TRole, string>, IDisposable
    where TRole : IdentityRole, new() {}

In the above, we can see there is a progression of interdependency among the types. IdentityUserRole is derived from IdentityUserRole<TKey> with a string as the single required type argument. IdentityRole is derived from IdentityRole<TKey, TIdentityUserRole> with the concrete types of string and the default implementation of IdentityUserRole (which, as we’ve seen, specifies a string as the key type), respectively, and so on. As we move down the list, the interdependency between concrete type implementations increases.

We will see how this impacts our ability to customize the User and Role types in a bit. First, we can see that adding simple properties to the ApplicationUser implementation provided with the Identity Samples project is about as easy as it can get.

Extending Identity User – The Easy Part

If all we want to do is add some additional properties to the default ApplicationUser class defined in the Identity Samples project, life is simple enough – the Identity Samples team has set the project up with a sensible default implementation which can be extended with very little effort.

Recall from earlier the ApplicationUser class. Say we want to add some address properties as follows:

Extending the Default ApplicationUser Class:
public class ApplicationUser : IdentityUser
{
    public async Task<ClaimsIdentity> 
        GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
    {
        var userIdentity = await manager
            .CreateIdentityAsync(this, 
                DefaultAuthenticationTypes.ApplicationCookie);
        return userIdentity;
    }
  
    public string Address { get; set; }
    public string City { get; set; }
    public string State { get; set; }
  
    // Use a sensible display name for views:
    [Display(Name = "Postal Code")]
    public string PostalCode { get; set; }
  
    // Concatenate the address info for display in tables and such:
    public string DisplayAddress
    {
        get
        {
            string dspAddress = 
                string.IsNullOrWhiteSpace(this.Address) ? "" : this.Address;
            string dspCity = 
                string.IsNullOrWhiteSpace(this.City) ? "" : this.City;
            string dspState = 
                string.IsNullOrWhiteSpace(this.State) ? "" : this.State;
            string dspPostalCode = 
                string.IsNullOrWhiteSpace(this.PostalCode) ? "" : this.PostalCode;
                
            return string
                .Format("{0} {1} {2} {3}", dspAddress, dspCity, dspState, dspPostalCode);
        }
    }
}

From here, in this limited case, all we need to do is update the various ViewModels, Views, and Controllers to incorporate our new properties. We will add functionality for the new properties to our RegisterViewModel, the Register.cshtml View itself, and the Register method of the AccountsController.

We will also do the same for the UsersAdminController and associated ViewModels and Views.

Update the Register ViewModel to Include Address Information

The RegisterViewModel is defined in the AccountViewModels.cs file. We need to add our new properties to this VeiwModel in order that the Register view, by which new users can sign up, affords them the opportunity to input their address information:

Update the RegisterViewModel to Include Address Info:
public class RegisterViewModel
{
    [Required]
    [EmailAddress]
    [Display(Name = "Email")]
    public string Email { 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; }
  
    // Add the new address properties:
    public string Address { get; set; }
    public string City { get; set; }
    public string State { get; set; }
  
    // Use a sensible display name for views:
    [Display(Name = "Postal Code")]
    public string PostalCode { get; set; }
}

Update the Register View to Include Address Information

Obviously, we want users to be able to input address information when they sign up. The Register.cshtml View is located in the Views => Accounts folder in the Solution Explorer. Update as follows:

Update the Register View with Address Information:
@model IdentitySample.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("", new { @class = "text-danger" })
    <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">
        @Html.LabelFor(m => m.Address, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.Address, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.City, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.City, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.State, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.State, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.PostalCode, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.PostalCode, 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>
    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input type="submit" class="btn btn-default" value="Register" />
        </div>
    </div>
}
  
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

We can see in the yellow highlighted area above where we have added the appropriate fields to our view template.

Update the Register Method on AccountController

Now we need to make sure the Address info is saved when the form data is submitted. Update the Register() method on the AccountController:

Update the Register Method on AccountController:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
    if (ModelState.IsValid)
    {
        var user = new ApplicationUser 
        { 
            UserName = model.Email, 
            Email = model.Email 
        };
  
        // Add the Address properties:
        user.Address = model.Address;
        user.City = model.City;
        user.State = model.State;
        user.PostalCode = model.PostalCode;
 
        var result = await UserManager.CreateAsync(user, model.Password);
        if (result.Succeeded)
        {
            var code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
            var callbackUrl = Url.Action("ConfirmEmail", "Account", 
                new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
            await UserManager.SendEmailAsync(user.Id, 
                "Confirm your account", 
                "Please confirm your account by clicking this link: <a href=\"" 
                + callbackUrl + "\">link</a>");
            ViewBag.Link = callbackUrl;
            return View("DisplayEmail");
        }
        AddErrors(result);
    }
  
    // If we got this far, something failed, redisplay form
    return View(model);
}

Update the Users Admin Components to Use the New Properties

Now, the basic registration functionality has been updated to utilize the new Address properties. However, the Identity Samples project also provides some administrative functionality by which a member of the Admin role can view and edit user information.

We need to update a few ViewModels, Views, and Controller methods here as well.

Update the UsersAdmin/Create.cshtml User View

In the Views => UsersAdmin folder, the Create.cshtml View uses the now-familiar RegisterViewModel to allow system administrators to add new users to the system. We want to afford data entry of Address information here, too:

Update the UsersAdmin/Create.cshtml View:
@model IdentitySample.Models.RegisterViewModel
@{
    ViewBag.Title = "Create";
}
  
<h2>@ViewBag.Title.</h2>
  
@using (Html.BeginForm("Create", "UsersAdmin", FormMethod.Post, 
    new { @class = "form-horizontal", role = "form" }))
{
    @Html.AntiForgeryToken()
    <h4>Create a new account.</h4>
    <hr />
    @Html.ValidationSummary("", new { @class = "text-error" })
    <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">
        @Html.LabelFor(m => m.Address, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.Address, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.City, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.City, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.State, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.State, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.PostalCode, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.PostalCode, 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>
    <div class="form-group">
        <label class="col-md-2 control-label">
            Select User Role
        </label>
        <div class="col-md-10">
            @foreach (var item in (SelectList)ViewBag.RoleId)
            {
                <input type="checkbox" name="SelectedRoles" 
                    value="@item.Value" class="checkbox-inline" />
                @Html.Label(item.Value, new { @class = "control-label" })
            }
        </div>
    </div>
    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input type="submit" class="btn btn-default" value="Create" />
        </div>
    </div>
}
  
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

Again, we can see where we need to update the View by the highlighted area above.

Update the Edit User ViewModel

The EditUserViewModel is used by the UserAdminController and associated Views to support editing user information. We need to include any new properties here that we want to be able to edit. The EditUserViewModel is defined in the AdminViewModels.cs code file. Update as follows:

Update the EditUserViewModel:
public class EditUserViewModel
{
    public string Id { get; set; }
  
    [Required(AllowEmptyStrings = false)]
    [Display(Name = "Email")]
    [EmailAddress]
    public string Email { get; set; }
  
    // Add the Address Info:
    public string Address { get; set; }
    public string City { get; set; }
    public string State { get; set; }
  
    // Use a sensible display name for views:
    [Display(Name = "Postal Code")]
    public string PostalCode { get; set; }
  
    public IEnumerable<SelectListItem> RolesList { get; set; }
}

Update the EditUser.cshtml View

Now that our EditUserViewModel has been updated, we again need to add the corresponding fields to the EditUser.cshtml View, also located in the Views => UsersAdmin folder:

Update the EditUser.cshtml View:
@using (Html.BeginForm()) {
    @Html.AntiForgeryToken()
  
    <div class="form-horizontal">
        <h4>Edit User Form.</h4>
        <hr />
        @Html.ValidationSummary(true)
        @Html.HiddenFor(model => model.Id)
  
        <div class="form-group">
            @Html.LabelFor(model => model.Email, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
               @Html.TextBoxFor(m => m.Email, new { @class = "form-control" })
               @Html.ValidationMessageFor(model => model.Email)
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model => model.Address, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.TextBoxFor(m => m.Address, new { @class = "form-control" })
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model => model.City, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.TextBoxFor(m => m.City, new { @class = "form-control" })
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model => model.State, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.TextBoxFor(m => m.State, new { @class = "form-control" })
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model => model.PostalCode, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.TextBoxFor(m => m.PostalCode, new { @class = "form-control" })
            </div>
        </div>
        <div class="form-group">
            @Html.Label("Roles", new { @class = "control-label col-md-2" })
            <span class=" col-md-10">
                @foreach (var item in Model.RolesList)
                {
                    <input type="checkbox" name="SelectedRole" value="@item.Value" checked="@item.Selected" class="checkbox-inline" />
                    @Html.Label(item.Value, new { @class = "control-label" })
                }
            </span>
        </div>
  
        <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>
}
  
<div>
    @Html.ActionLink("Back to List", "Index")
</div>
  
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

Update the Users Admin Index, Delete, and Detail Views

For the UserAdmin Index, Delete, and Detail Views, we are going to do something a little different. We will use the DisplayAddress property to concatenate the address info into a single line suitable for display in a table or single form label. For the sake of brevity, we will only update the Index view here, also found in Views => UsersAdmin:

Update the UsersAdmin Index.cshtml View:
@model IEnumerable<IdentitySample.Models.ApplicationUser>
  
@{
    ViewBag.Title = "Index";
}
  
<h2>Index</h2>
  
<p>
    @Html.ActionLink("Create New", "Create")
</p>
<table class="table">
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.UserName)
        </th>
        @*Add a table header for the Address info:*@
        <th>
            @Html.DisplayNameFor(model => model.DisplayAddress)
        </th>
        <th>
  
        </th>
    </tr>
  
    @foreach (var item in Model)
    {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.UserName)
            </td>
            <td>
                @*Add table data for the Address info:*@
                @Html.DisplayFor(modelItem => item.DisplayAddress)
            </td>
            <td>
                @Html.ActionLink("Edit", "Edit", new { id = item.Id }) |
                @Html.ActionLink("Details", "Details", new { id = item.Id }) |
                @Html.ActionLink("Delete", "Delete", new { id = item.Id })
            </td>
        </tr>
    }
  
</table>

We can see in the above that all we really did was add a table header element and a table data element to display the DisplayAddress data (which is a function masquerading as a property). The very same thing can be done for the Delete.cshtml View and the  Details.cshtml View, so we wont do that here.

Update the User Admin Controller

Now that we have updated the relevant ViewModel and Views, we also need to update the corresponding controller actions on the UserAdminController so that model data is properly passed to and from the Views. Specifically, we need to modify the Create() and Edit() methods.

Update the Create Method on UserAdminController

The create method allows an administrator to create a new system user. Add the functionality to include the new Address properties when the new user is created:

Modified Create method on UserAdminController:
[HttpPost]
public async Task<ActionResult> Create(RegisterViewModel userViewModel, params string[] selectedRoles)
{
    if (ModelState.IsValid)
    {
        var user = new ApplicationUser 
        { 
            UserName = userViewModel.Email, Email = 
            userViewModel.Email, 
            // Add the Address Info:
            Address = userViewModel.Address,
            City = userViewModel.City,
            State = userViewModel.State,
            PostalCode = userViewModel.PostalCode
        };
  
        // Add the Address Info:
        user.Address = userViewModel.Address;
        user.City = userViewModel.City;
        user.State = userViewModel.State;
        user.PostalCode = userViewModel.PostalCode;
  
        // Then create:
        var adminresult = await UserManager.CreateAsync(user, userViewModel.Password);
  
        //Add User to the selected Roles 
        if (adminresult.Succeeded)
        {
            if (selectedRoles != null)
            {
                var result = await UserManager.AddToRolesAsync(user.Id, selectedRoles);
                if (!result.Succeeded)
                {
                    ModelState.AddModelError("", result.Errors.First());
                    ViewBag.RoleId = new SelectList(await RoleManager.Roles.ToListAsync(), "Name", "Name");
                    return View();
                }
            }
        }
        else
        {
            ModelState.AddModelError("", adminresult.Errors.First());
            ViewBag.RoleId = new SelectList(RoleManager.Roles, "Name", "Name");
            return View();
        }
        return RedirectToAction("Index");
    }
    ViewBag.RoleId = new SelectList(RoleManager.Roles, "Name", "Name");
    return View();
}

Next, update the Edit() method in a similar manner. First, we need to populate the EditUserViewModel with the Address info before we pass it to the View from the [Get] method:

Modified [Get] Implementation for UserAdmin Controller Edit Method:
public async Task<ActionResult> Edit(string id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    var user = await UserManager.FindByIdAsync(id);
    if (user == null)
    {
        return HttpNotFound();
    }
  
    var userRoles = await UserManager.GetRolesAsync(user.Id);
  
    return View(new EditUserViewModel()
    {
        Id = user.Id,
        Email = user.Email,
        // Include the Addresss info:
        Address = user.Address,
        City = user.City,
        State = user.State,
        PostalCode = user.PostalCode,
        RolesList = RoleManager.Roles.ToList().Select(x => new SelectListItem()
        {
            Selected = userRoles.Contains(x.Name),
            Text = x.Name,
            Value = x.Name
        })
    });
}

Then, we need to update the [Post] override. Take careful note, though, to also include the additional bindings in the arguments:

Modified Edit Method on UserAdminController:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit([Bind(Include = 
    "Email,Id,Address,City,State,PostalCode")] 
    EditUserViewModel editUser, params string[] selectedRole)
{
    if (ModelState.IsValid)
    {
        var user = await UserManager.FindByIdAsync(editUser.Id);
        if (user == null)
        {
            return HttpNotFound();
        }
 
        user.UserName = editUser.Email;
        user.Email = editUser.Email;
        user.Address = editUser.Address;
        user.City = editUser.City;
        user.State = editUser.State;
        user.PostalCode = editUser.PostalCode;
  
        var userRoles = await UserManager.GetRolesAsync(user.Id);
        selectedRole = selectedRole ?? new string[] { };
        var result = await UserManager.AddToRolesAsync(user.Id, 
            selectedRole.Except(userRoles).ToArray<string>());
  
        if (!result.Succeeded)
        {
            ModelState.AddModelError("", result.Errors.First());
            return View();
        }
        result = await UserManager.RemoveFromRolesAsync(user.Id, 
            userRoles.Except(selectedRole).ToArray<string>());
        if (!result.Succeeded)
        {
            ModelState.AddModelError("", result.Errors.First());
            return View();
        }
        return RedirectToAction("Index");
    }
    ModelState.AddModelError("", "Something failed.");
    return View();
}

Role-Based Authorization is Already Implemented

When we looked at customizing Identity 1.0 in the article Extending Identity User and Implementing Role-Based Authorization, we needed significantly modify the basic project in order to assign users to roles. There was no provision in the default ASP.NET MVC project to directly manage user-role assignment.

The Identity Samples project addressed this deficiency, and has implemented user/role management out of the box. Where we previously needed to roll our own controller methods, models, and views in order to display and select roles for each user, this functionality is now included out of the box, in a manner very similar to what we had to do ourselves previously. However, what this means is that we need to make sure we initialize the application with a pre-built admin user.

Take Note of The IdentityConfig File

Within the Identity Samples project, database initialization and seeding is handled in the App_Start => IdentityConfig.cs file. For now, we don’t need to make any changes to this file. However, note the ApplicationDbInitializer class, in which we define an initial Admin user, and initial Role, and a few other database configuration items:

The ApplicationDbInitializer Class in IdentityConfig.cs
public class ApplicationDbInitializer 
    : DropCreateDatabaseIfModelChanges<ApplicationDbContext> 
{
    protected override void Seed(ApplicationDbContext context) {
        InitializeIdentityForEF(context);
        base.Seed(context);
    }
  
    //Create User=Admin@Admin.com with password=Admin@123456 in the Admin role        
    public static void InitializeIdentityForEF(ApplicationDbContext db) {
        var userManager = 
            HttpContext.Current.GetOwinContext()
                .GetUserManager<ApplicationUserManager>();
        var roleManager = 
            HttpContext.Current.GetOwinContext().Get<ApplicationRoleManager>();
        const string name = "admin@example.com";
        const string password = "Admin@123456";
        const string roleName = "Admin";
  
        //Create Role Admin if it does not exist
        var role = roleManager.FindByName(roleName);
        if (role == null) {
            role = new IdentityRole(roleName);
            var roleresult = roleManager.Create(role);
        }
  
        var user = userManager.FindByName(name);
        if (user == null) {
            user = new ApplicationUser { UserName = name, Email = name };
            var result = userManager.Create(user, password);
            result = userManager.SetLockoutEnabled(user.Id, false);
        }
  
        // Add user admin to Role Admin if not already added
        var rolesForUser = userManager.GetRoles(user.Id);
        if (!rolesForUser.Contains(role.Name)) {
            var result = userManager.AddToRole(user.Id, role.Name);
        }
    }
}

Also note in the above that, in the default code the ApplicationDbInitializer class is derived from DropCreateDatabaseIfModelChanges<ApplicationDbContext> . As the name implies, this will cause a new database to be created, replacing the old, only when changes to the model impact the database schema. Often, this is sufficient. However, sometimes, it is handy to start with a fresh database each time the project is run. In these cases, we can simply change the class declaration to derive from DropCreateDatabaseAlways<ApplicationDbContext> which again, as the name implies, will cause a fresh database to be initialized each time the application is run.

For now, all we need to be aware of is the default admin user defined in the InitializeIdentityForEF() method, so we know how to log in for the first time.

Running the Project with the IdentityUser Modifications in Place

Thus far, all we have really done is add a few new properties to the existing implementation of the ApplicationUser model, and updated the corresponding ViewModels, Views, and Controllers. However, to extend IdentityUser in this manner, that is all that is needed. The project should now run, and the Address properties we have added should be properly represented within our application.

If we run the project, log in as the user defined in the IdentityConfig.cs file, we have admin access to the users and roles:

Logged In to the Identity Samples Application:

initial-login-modified-user

If we select the UsersAdmin tab, we find a list, which includes (to this point) only the seeded admin user, and in fact the Address information is blank. That’s because we didn’t seed any address values:

The Users Admin Tab of the Identity Samples Project with Empty Address:

select-users-admin-before-edit

We can now navigate to the Edit View, and update our initial user with some address information:

Edit the Default Admin User and Add Address Info:

edit-user-before-save

Once we have entered our address information and save, the list is updated, and the Users Admin Index view displays the updated info:

The Updated Users Admin Tab:

admin-user-index-after-edit

Things should work in a similar manner if we were to create a new user by navigating to the Create New link on the Admin Users tab, and also if we were to log out, and register as a new user.

Extending the basic IdentityUser implementation was simple enough, and it appears the Identity Samples project was fairly designed with this in mind. However, when it comes to extending or modifying the IdentityRole implementation, things become a little more complicated.

Extending Identity Role

As we noted earlier, the code Identity 2.0 framework was designed with a great deal of flexibility in mind, through the use of generic types and generic methods in the base classes used for key components. We saw previously how this creates a very flexible set of models, but working with them can be a little tricky when they become interdependent.

We want to use our existing, modified Identity Samples project, and add further customizations to the Identity Role implementation. Note that out of the box, Identity Samples does not define an ApplicationRole class – the project relies upon the basic IdentityRole provided by the framework itself.

The Identity Samples project simply uses the default implementation of the IdentityRole class defined in the namespace Microsoft.AspNet.Identity.EntityFramework. As we saw earlier, the default definition for IdentityRole looks like this:

The Default IdentityRole Implementation:
public class IdentityRole : IdentityRole<string, IdentityUserRole>
{
    public IdentityRole()
    {
        base.Id = Guid.NewGuid().ToString();
    }
  
    public IdentityRole(string roleName) : this()
    {
        base.Name = roleName;
    }
}

Again, as we discussed earlier, the Identity team has created a sensible default implementation by deriving from IdentityRole<TKey, TUserRole>, passing in a string and the Identity framework type IdentityUserRole as type arguments to the generic class definition.

If we wish to extend this implementation to include some custom properties, we will need to define our own. We can do this by inheriting directly from the default, and adding one or more of our own properties. We could, alternatively, start from the bottom and create our own implementation by deriving from IdentityRole<Tkey, TUserRole> but for our purposes here, we have no reason to start that low in the abstraction chain. We are sticking with the default string key type, and the basic IdentityUserRole.

A Note About IdentityRole and IdentityUserRole

Let’s pause for a second to note a potential point of confusion. The Identity framework defines two seemingly similar classes, IdentityRole and IdentityUserRole. At the lowest framework implementation level, both are generic classes which implement specific interfaces. As suggested above, the generic implementation of IdentityRole looks like this:

Base implementation of the IdentityRole Class in Identity Framework 2.0:
public class IdentityRole<TKey, TUserRole> : IRole<TKey>
where TUserRole : IdentityUserRole<TKey>
{
    public TKey Id
    {
        get
        {
            return JustDecompileGenerated_get_Id();
        }
        set
        {
            JustDecompileGenerated_set_Id(value);
        }
    }
    public string Name
    {
        get;
        set;
    }
    public ICollection<TUserRole> Users
    {
        get
        {
            return JustDecompileGenerated_get_Users();
        }
        set
        {
            JustDecompileGenerated_set_Users(value);
        }
    }
    public IdentityRole()
    {
        this.Users = new List<TUserRole>();
    }
}

Meanwhile, the generic base IdentityUserRole class looks like this:

The Generic Base Implementation for IdentityUserRole:
public class IdentityUserRole<TKey>
{
    public virtual TKey RoleId
    {
        get;
        set;
    }
    public virtual TKey UserId
    {
        get;
        set;
    }
    public IdentityUserRole()
    {
    }
}

We don’t need to worry overly much about these low-level details for our purposes here, other than to note that IdentityRole and IdentityUserRole are two different classes, with two different purposes. IdentityRole represents an actual Role entity in our application and in the database, while IdentityUserRole represents the relationship between a User and a Role.

With the similar names, it is easy to confuse one with the other in the midst of typing out code, and particularly when relying on VS intellisense. Of course, the compiler will let you know if you confuse the two, but it is still good to remain cognizant of the distinction, particularly when attempting more advanced customizations.

Adding a Customized Role to the Identity Samples Project

Bearing all of the above in mind, let’s add a modified Role definition to our project. In keeping with the convention used for the project implementation of IdentityUser (“ApplicationUser”), we will add a class to the IdentityModels.cs file named ApplicationRole which inherits from IdentityRole and implements a custom Description property:

A Custom Implementation Derived from the Default IdentityRole Class:
public class ApplicationRole : IdentityRole
{
    public ApplicationRole() : base() { }
    public ApplicationRole(string name) : base(name) { }
    public string Description { get; set; }
}

Now, that wasn’t too bad, right? Well, we’re only just getting started here. Since we are no longer using the default IdentityRole implementation, if we want to actually USE our custom class in the Identity Samples project, we need to introduce some non-trivial changes in a number of places.

Re-Implementing RoleStore and ApplicationRoleManager

First off, if we take another look at the App_Start => IdentityConfig.cs file, we find a class definition for ApplicationRoleManager:

Existing Implementation of ApplicationRoleManager in Identity Samples Project:
public class ApplicationRoleManager : RoleManager<IdentityRole>
{
    public ApplicationRoleManager(IRoleStore<IdentityRole,string> roleStore)
        : base(roleStore)
    {
    }
  
    public static ApplicationRoleManager Create(
        IdentityFactoryOptions<ApplicationRoleManager> options, IOwinContext context)
    {
        return new ApplicationRoleManager(
            new RoleStore<IdentityRole>(context.Get<ApplicationDbContext>()));
    }
}

As we can see, this class is rather heavily dependent upon the default framework type IdentityRole. We will need to replace all of the references to the IdentityRole type with our own ApplicationRole implementation.

Modified ApplicationRoleManager Class Depends on Custom ApplicationRole:
public class ApplicationRoleManager : RoleManager<ApplicationRole>
{
    public ApplicationRoleManager(
        IRoleStore<ApplicationRole,string> roleStore)
        : base(roleStore)
    {
    }
    public static ApplicationRoleManager Create(
        IdentityFactoryOptions<ApplicationRoleManager> options, IOwinContext context)
    {
        return new ApplicationRoleManager(
            new RoleStore<ApplicationRole>(context.Get<ApplicationDbContext>()));
    }
}

Also, in the InitializeDatabaseForEF() method in our ApplicationDbInitializer class (also in the IdentityConfig.cs file), we need to initialize a new ApplicationRole instead of a new IdentityRole:

Initialize ApplicationRole in Database Set-Up:
public static void InitializeIdentityForEF(ApplicationDbContext db) {
    var userManager = 
        HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>();
    var roleManager = 
        HttpContext.Current.GetOwinContext().Get<ApplicationRoleManager>();
    const string name = "admin@example.com";
    const string password = "Admin@123456";
    const string roleName = "Admin";
  
    //Create Role Admin if it does not exist
    var role = roleManager.FindByName(roleName);
    if (role == null) {
        role = new ApplicationRole(roleName);
        var roleresult = roleManager.Create(role);
    }
   
    var user = userManager.FindByName(name);
    if (user == null) {
        user = new ApplicationUser { UserName = name, Email = name };
        var result = userManager.Create(user, password);
        result = userManager.SetLockoutEnabled(user.Id, false);
    }
  
    // Add user admin to Role Admin if not already added
    var rolesForUser = userManager.GetRoles(user.Id);
    if (!rolesForUser.Contains(role.Name)) {
        var result = userManager.AddToRole(user.Id, role.Name);
    }
}

Update the Create Method on the Roles Admin Controller

Similar to the InitializeDatabaseForEF() method, we also need to properly initialize a new instance of ApplicationRole instead of IdentityRole in the Create() method on the RolesAdminController:

Update the Create Method on RolesAdminController:
public async Task<ActionResult> Create(RoleViewModel roleViewModel)
{
    if (ModelState.IsValid)
    {
        // Initialize ApplicationRole instead of IdentityRole:
        var role = new ApplicationRole(roleViewModel.Name);
        var roleresult = await RoleManager.CreateAsync(role);
        if (!roleresult.Succeeded)
        {
            ModelState.AddModelError("", roleresult.Errors.First());
            return View();
        }
        return RedirectToAction("Index");
    }
    return View();
}

Now we need to make sure we can consume our new and improved Role implementation in our Views. As we did with the modified ApplicationUser class, we now need to accommodate or new Role implementation in our ViewModels and View, and make sure we are passing the property data between the controllers and views appropriately.

Add Extended Properties to the RoleViewModel

In the AdminViewModels.cs file, we need to update the definition for RoleViewModel by adding our new Description property:

The Updated RoleViewModel:
public class RoleViewModel
{
    public string Id { get; set; }
    [Required(AllowEmptyStrings = false)]
    [Display(Name = "RoleName")]
    public string Name { get; set; }
    public string Description { get; set; }
}

Next, we need to make sure the appropriate views make the Description property available for display and/or form entry.

Update the Roles Admin Create.cshtml View

The Views we need to update are in the Views => RolesAdmin folder in the VS Solution Explorer.

Similar to what we did with the Views for User Admin, we need to make the Description property available so that when administrators create new Roles, they can also enter and save the description. Add a form element to the Views => RolesAdmin => Create.cshtml view for the Description property:

The Updated Create.cshtml View for Role Admin:
@model IdentitySample.Models.RoleViewModel
  
@{
    ViewBag.Title = "Create";
}
  
<h2>Create.</h2>
  
@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()
  
    <div class="form-horizontal">
        <h4>Role.</h4>
        <hr />
        @Html.ValidationSummary(true)
  
        <div class="form-group">
            @Html.LabelFor(model => model.Name, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.TextBoxFor(model => model.Name, new { @class = "form-control" })
                @Html.ValidationMessageFor(model => model.Name)
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model => model.Description, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.TextBoxFor(model => model.Description, new { @class = "form-control" })
                @Html.ValidationMessageFor(model => model.Description)
            </div>
        </div>
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </div>
    </div>
}
  
<div>
    @Html.ActionLink("Back to List", "Index")
</div>
  
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

Update the Roles Admin Edit.cshtml View

Next, we do a similar modification to the Views => RolesAdmin => Edit.cshtml View:

The Modified Roles Admin Edit.cshtml View:
@model IdentitySample.Models.RoleViewModel
  
@{
    ViewBag.Title = "Edit";
}
  
<h2>Edit.</h2>
  
@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()
      
    <div class="form-horizontal">
        <h4>Roles.</h4>
        <hr />
        @Html.ValidationSummary(true)
        @Html.HiddenFor(model => model.Id)
        <div class="form-group">
            @Html.LabelFor(model => model.Name, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.TextBoxFor(model => model.Name, new { @class = "form-control" })
                @Html.ValidationMessageFor(model => model.Name)
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model => model.Description, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.TextBoxFor(model => model.Description, new { @class = "form-control" })
                @Html.ValidationMessageFor(model => model.Description)
            </div>
        </div>
        <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>
}
  
<div>
    @Html.ActionLink("Back to List", "Index")
</div>
  
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

Update the Roles Admin Index.cshtml View

Unlike the Create and Edit Views, the Index, Delete, and Detail views need a few additional adjustments. Note that, in their existing form, these three View templates expect an instance of List<IdentityRole> as the model to be passed from the controller. We need to change the very first line of code to expect an IEnumerable<ApplicationRole> instead. Then, we make a relatively simple addition to the table header and table row elements in order to display the Description property:

The Updated Roles Admin Index.cshtml View
@model IEnumerable<IdentitySample.Models.ApplicationRole>
  
@{
    ViewBag.Title = "Index";
}
  
<h2>Index</h2>
  
<p>
    @Html.ActionLink("Create New", "Create")
</p>
<table class="table">
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.Name)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Description)
        </th>
        <th>
        </th>
    </tr>
  
    @foreach (var item in Model)
    {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Name)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Description)
            </td>
            <td>
                @Html.ActionLink("Edit", "Edit", new { id = item.Id }) |
                @Html.ActionLink("Details", "Details", new { id = item.Id }) |
                @Html.ActionLink("Delete", "Delete", new { id = item.Id })
            </td>
        </tr>
    }
  
</table>
    
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

The Delete.cshtml and the Details.cshtml Views can be modified in a similar fashion, so we won’t do that here in the interest of brevity. Note, however, that for the Index View, the View expects an IEnnumerable<ApplicationRole> whereas the Details and Delete Views will expect a singular instance of ApplicationRole.

Updating The Roles Admin Controller

In order for all this to work, we now need to modify the Create() and Edit() methods on the RolesAdminController so that the proper data is passed to and from the corresponding Views, and properly persisted to the backing store.

Update the Create Method on the Roles Admin Controller

The create method receives an instance of ApplicationRole as form data and persists the new Role to the database. All we need to do here is make sure the new Description data is also saved:

The Updated Create Method on Roles Admin Controller:
[HttpPost]
public async Task<ActionResult> Create(RoleViewModel roleViewModel)
{
    if (ModelState.IsValid)
    {
        var role = new ApplicationRole(roleViewModel.Name);
  
        // Save the new Description property:
        role.Description = roleViewModel.Description;
        var roleresult = await RoleManager.CreateAsync(role);
        if (!roleresult.Succeeded)
        {
            ModelState.AddModelError("", roleresult.Errors.First());
            return View();
        }
        return RedirectToAction("Index");
    }
    return View();
}

Update the Edit Method on the Roles Admin Controller

There are a few additional items to tend to with the Edit() method. First off, we need to populate the form with the current data in the GET request, then, when the POST comes back, we need to bind the appropriate form data, and make sure the new Description property is saved along with everything else.

Update the Edit Method on Roles Admin Controller:
public async Task<ActionResult> Edit(string id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    var role = await RoleManager.FindByIdAsync(id);
    if (role == null)
    {
        return HttpNotFound();
    }
    RoleViewModel roleModel = new RoleViewModel 
    { 
        Id = role.Id, 
        Name = role.Name 
    };
  
    // Update the new Description property for the ViewModel:
    roleModel.Description = role.Description;
    return View(roleModel);
}
  
  
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit([Bind(
    Include = "Name,Id,Description")] 
    RoleViewModel roleModel)
{
    if (ModelState.IsValid)
    {
        var role = await RoleManager.FindByIdAsync(roleModel.Id);
        role.Name = roleModel.Name;
  
        // Update the new Description property:
        role.Description = roleModel.Description;
        await RoleManager.UpdateAsync(role);
        return RedirectToAction("Index");
    }
    return View();
}

Running the Project with Role Modifications in Place

If we have been careful as we updated all of our views and controllers, we should now be able to see our extended role in action. If we run the project, log in, and navigate to the RoleAdmin tab, we find a list containing a single Role. The description field is blank at this point, because we didn’t add a description to the pre-defined initial role in our seed method.

The Roles Admin Tab at Startup:

role-admin-index

If we choose to Edit the existing role, we see we can type in a new description:

The Edit Role View – Data Entry:

edit-role-before-save

Once we save the new entry, we can see that the Index View now displays the Role Description:

The Roles Admin Index View After Edit:

role-admin-index-after-save

Wrapping It Up

In this article, we’ve taken a look at how to extend and modify the key IdentityUser and IdentityRole components of the Identity 2.0 framework. We have Done so in the context of the Identity Samples project, which provides a strong platform for both learning how to implement Identity 2.0 for basic authentication and authorization purposes, as well as a great foundation for the Identity portion of your own web application.

Items to keep in mind are:

The Identity Samples project is an alpha release, and is likely to evolve over time – there may be future changes which impact the specifics of this article.

Identity 2.0 RTM brings substantial flexibility and a host of additional capabilities to the ASP.NET platform, capabilities which until now had been notably missing. We have only scratched the surface here.

Identity 2.0 and the Identity Samples project present a simplified abstraction over a more flexible, and more complex underlying model. Customization is possible to a greater degree, with fewer hack-like work-arounds. However, the steps necessary are not necessarily immediately apparent. The framework of generically-typed components requires some additional thought when customizing due to the inter-dependent nature of the components.

As developers, it is within our power to dig in and explore, and figure things out. We get to do a lot of that as we set out to maximize the benefit of Identity 2.0 to our applications, and learn the new framework in general.

Additional Resources and Items of Interest

Identity 2.0 Resources:

ASP.Net
ASP.NET MVC: Add a Select All Checkbox to a Checklist Table Using JQuery
C#
Modeling a Directory Structure on Azure Blob Storage
C#
C#: Building a Useful, Extensible .NET Console Application Template for Development and Testing
  • Dehanys

    DehanysDehanys

    Author Reply

    Hello John.
    This is a great article. Actually it is the only article I found that comes closer to what I want to do.

    In my organization users that are basic users for one application can be administrators in another, and I would like them to have the same Identity provider for all. What would happen if, instead of a description column, I add an AppId column to have my roles respond to different applications? In this case how can I Find a Role By User and AppId at the same time?
    Maybe It cannot be done by adding the AppId column and there is another way. In any case I would appreciate if you point me to the right direction.


  • Jean-Paul

    Jean-PaulJean-Paul

    Author Reply

    Great article! Unfortunately you do not mention how to override `IdentityUserRole`I want to expand the UserRole with a ‘CreateDate’ and ‘UpdateDate’ including some others.


  • Brian Carpenter

    Nice.

    I’m hoping that you create new article for ASP.NET Identity 3.0 (ASP.NET Core Identity)

    Thank you.


  • Dominique GRATPAIN

    It seems a good article but ..
    – it is for Asp.Net Core (and Asp.Net (not Core) exists allways in 2018)
    – it is for MVC (and Web Forms exists allways in 2018)
    – it is written in 2014 and now (2018 july) Microsoft did change with Asp.net Identity

    Now my question :

    Have you (or can you plan) the same article and code :
    – up do date
    – in Asp.net and Asp.net Core
    – in MVC and Web Forms

    I think that is the job of Microsoft but Microsoft don’t do it.


  • Norberto

    NorbertoNorberto

    Author Reply

    Hello. I have been looking for a web tutorial to follow for a month and set up my web application. The security part is so important and that’s why I decided to select the best information. This tutorial is incredible, it helped me in getting things work pretty easily. Understanding the basis and giving me an overview of the entire Identity 2.0 framework, how things tie togheter.

    Clap Clap! We need more knowledge embassadors like you! Thanks


  • George Elam

    John, Great Job!!
    Everything works up to the end of your article in my project.
    I’m having trouble with the next step, which is getting the UserManager.AddToRole method to work. Are you still around, and can you give a hint about this. Feel free to contact me offline. thanks!!


  • Nafiul

    NafiulNafiul

    Author Reply

    when I add “Description” field to Role, in the code-first approach it will re-create database and 2 columns will be added to aspNetRoles instead of 1. The additional column name is “Discriminator” type: nvarchar(128) not null. When I’m adding more roles the “Discriminator” column will automatically get value “ApplicationRole”.

    How do i get rid of this column? – Thanks in advance.


  • Roger Mokarzel

    Excellent article!! really helped me to well understand the identity 2.x :) Thank you very much, John!!


  • kewin rather

    Your this post wonderful and very useful. It will hold a light to my login system. Thank you so much for all of these.


  • Aldo Tabone

    Great article…

    Is it possible to add a composite key to AspNetUserRoles from this example? I’m getting a foreign key instead.

    -> IdentityModels.cs

    public class ApplicationUserRoles : IdentityUserRole
    {
    public int ClientId { get; set; }
    public virtual Clients Clients { get; set; }
    }


  • Hussain Alhubail

    One of the best if not the only best Article on Identity 2.0 and how to use it.

    Thank You very much.


  • Bimal

    BimalBimal

    Author Reply

    Great post sir. Can you tell me how to authenticate user based on their role and orgalizationId ?


  • Norman

    NormanNorman

    Author Reply

    Thanks for this! Really helpful.

    Just a suggestion. If you Edit (PostMethod), you may also want to check if:

    var roleResult = RoleManager.Update(role);

    if (!roleResult.Succeeded)
    {
    ModelState.AddModelError(“”, roleResult.Errors.First());
    return View();
    }

    As there is a chance someone may change the name of a Role to an already existing one. But great job with this :)


  • Sam Simon

    Sam SimonSam Simon

    Author Reply

    Great article, Ive followed it and created a great MVC 5 web app. Unfortunately I am looking for and still am looking for a way to add Custom Profile info into Web Forms NOT MVC, Ive searched but all tutorials I get are MVC,

    How do you do this tutorial exactly with WebForms?


    • John Atten

      John AttenJohn Atten

      Author Reply

      I haven’t worked with WebForms much AT ALL, so I really couldn;t say…although under the hood, at the code and database level I would expect it works the same.


  • Khedr

    KhedrKhedr

    Author Reply

    Great article.
    After customize my project to have just users and roles I ignore claims and logins table like this
    modelBuilder.Ignore();
    modelBuilder.Ignore();
    but I get error
    The property ‘Claims’ on type ‘ApplicationUser’ is not a navigation property. The Reference and Collection methods can only be used with navigation properties. Use the Property or ComplexProperty method.
    in line
    var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);

    Thank you


    • John Atten

      John AttenJohn Atten

      Author Reply

      It’s been a while, but as I recall, you can’t ignore the claims and logins – they are part of how Identity does its thing, regardless as to whether or not you are using them in your application.


  • Priom Biswas

    Thanks for your great article. I want to extend it further. I have a Product controller and i want to assign the create and edit method of that controller to a particular user. How can i do that?


    • John Atten

      John AttenJohn Atten

      Author Reply

      [Authorize(Users=”jimi,Janis,Jim,Kurt”)]


      • Priom Biswas

        I don’t want to hardcode this roles. I want to assign action to user from UI


  • Jose Bonilla

    Excellent tutorial.


  • George

    GeorgeGeorge

    Author Reply

    Great article! For some reason I don’t see the database. Am I missing something? I am wondering how I can migrate the application to use a stand-alone SQL server.


    • John Atten

      John AttenJohn Atten

      Author Reply

      @George – Sorry I took so long getting back to you. Hopefully you were able to solve the problem by now! The database will be built locally, on first run, in the App_Data folder, or possibly at the root of your user folder (there have been changes to how this works by default). If you want to target an actual SQL server instance, as I recall you just point your connection string at that server in Web.config. Make sure to specify the specific database if you do this. Let me know if you have trouble – it’s been a while since I’ve messed with that, to be honest, but as I recall it was pretty simple.


  • Joshua Lawrence Austill

    Thank you for taking the time to do this write-up. I am using the AspNet.Identity.MongoDB identity library and this article made adding descriptions to my roles a VERY fast task. I only had to figure out the MongoDB part instead of the EF part, which was super simple! Thanks again, you rock! If you are ever in Montana let me know, I’ll get you a drink :)


    • John Atten

      John AttenJohn Atten

      Author Reply

      @Joshua – Hey, thanks for reading, and likewise if you find yourself in the Denver area :-)


  • Kelum Priyadarshane

    but each line of above `Register` controller method that exist ‘RoleManager’ word I’m getting compile time error as below

    `Using the generic type ‘RoleManager’ requires 2 type arguments`

    which means I’m getting 3 compile time errors in above method.


    • Chris

      ChrisChris

      Author Reply

      You need to either be using the sample project or add the class to your identityconfig.cs

      public class ApplicationRoleManager : RoleManager
      {
      public ApplicationRoleManager(
      IRoleStore roleStore)
      : base(roleStore)
      {
      }
      public static ApplicationRoleManager Create(
      IdentityFactoryOptions options, IOwinContext context)
      {
      return new ApplicationRoleManager(
      new RoleStore(context.Get()));
      }
      }


  • Stephen Allott

    Great article. However, when I run the code downloaded from github I get the following error: Is there something I’m missing?

    Thanks in advance.

    Steve

    Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

    Exception Details: System.Data.SqlClient.SqlException: A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: SQL Network Interfaces, error: 50 – Local Database Runtime error occurred. Cannot create an automatic instance. See the Windows Application event log for error details.
    )

    Source Error:

    Line 158: user.PostalCode = model.PostalCode;
    Line 159:
    Line 160: var result = await UserManager.CreateAsync(user, model.Password);
    Line 161: if (result.Succeeded)
    Line 162: {


    • John Atten

      John AttenJohn Atten

      Author Reply

      How do you have your connection string configured?


  • John Atten

    John AttenJohn Atten

    Author Reply

    Hey, thanks for pointing this out… I think I may have been in a hurry when I did this part. I'm sure there's a mistake in there somewhere. I'll update the article when I have a look.

    Thanks again!

    :-)


  • Rick

    RickRick

    Author Reply

    Hey John,
    I know it's not your job to offer support – but have you seen this code in the rolesAdminController to confirm role deletion?

    public async Task<ActionResult> DeleteConfirmed(string id, string deleteUser)

    I don't think that second parameter is ever received – and even if it was the code proceeds to do this:
    if (deleteUser != null)
    {
    result = await RoleManager.DeleteAsync(role);
    }
    else
    {
    result = await RoleManager.DeleteAsync(role);
    }

    i.e. the code is the same – do you know what this is supposed to do? I assume the users in AspNetUserRoles are deleted by a cascade on delete constraint – so not sure what this DeleteUser string is all about.


  • John Atten

    John AttenJohn Atten

    Author Reply

    Thanks for following up Cindy!


  • Cindy

    CindyCindy

    Author Reply

    In case someone else runs into this issue I figured I would post an answer. By the way, I got the answer on stackoverflow:

    http://stackoverflow.com/questions/24846367/identity-2-1-0-samples-mvc-5

    Basically I installed the Update 2 of visual studio 2013. Now the red squigglies are gone and intelliSense works for my lambda expressions.


  • Rick

    RickRick

    Author Reply

    It's because it's in a static constructor:

    static ApplicationDbContext()
    {
    }

    so there's no guarantee as to when that will be called – it's only guaranteed to be called before any method / property in the class is accessed – so by not logging in or registering it will never get called.


  • John Atten

    John AttenJohn Atten

    Author Reply

    A. First, yeah, nothing happens to the database until you try to access it, so you are correct, simply starting the app will not cause the changes to occur. You need to either log-in, or register.

    B. You can also use DropCreateDatabaseAlways<ApplicationDbContext>, which will also only perform the action upon register or login.


  • Rick

    RickRick

    Author Reply

    You can forget previous question. It's weird – but it works. If you change the model, start the sample app and close it without doing anything it does not update the database. But if you actually login or register then it does update the database. So what is really triggering it is the real question?

    Thanks


  • Rick

    RickRick

    Author Reply

    Just a note – I had to enable code first migrations to get the database structures to regenerate when changing the ApplicationUser – this never seemed to do anything:

    DropCreateDatabaseIfModelChanges<ApplicationDbContext>

    Am I missing something? I note that this is referencing the generic <ApplicationDbContext> which is:

    ApplicationDbContext : IdentityDbContext<ApplicationUser>

    Should this run when ApplicationUser is changed? I am thinking so but I could never get it to run.

    Thanks


  • Cindy

    CindyCindy

    Author Reply

    Closing View/Building/Re-opening did not fix it for me. For what it's worth, it does not give me any compiling error, so I will just live with it for now. It bothers me, but oh well. :(

    Thanks for all the help John.


  • John Atten

    John AttenJohn Atten

    Author Reply

    On a final note – Once you've installed all the Identity stuff and a bunch of views, I think the build process is when VS "learns" how to parse the view code.

    I know that (for example) if you change the model upon which a view is based, you may see a bunch of red squigglies. Close View/Build/Re-open fixes this.


  • John Atten

    John AttenJohn Atten

    Author Reply

    No, it's not related to any bug. Unless I'm missing something, you really just need to Build the project, and that issue goes away.

    Also, I have found that close, build, re-open a view template fixes the issue as well.

    I think it has to do with the way VS, intellisense, and the compiler need to work with Razor templates. Unlike normal C# code, sometimes VS needs to be "reminded" how to parse the Razor code onscreen. REmember, VS is essentially working with multiple language paradigms in a view: C#/Razor, HTML, Javascript, and occasionally CSS.

    I think I've just gotten used to it. I don;t pretend to know all the details, other then to say it's relatively benign behavior, probably related to the above.


  • Cindy

    CindyCindy

    Author Reply

    Hi John, not a flaky question at all. Yes I did try running the project and it works fine. But since I am new to MVC, I was wondering if this is normal and it also make me nervous.

    Would you mind trying creating a new project (empty)and then open the Package Manager console and type: Install-Package Microsoft.AspNet.Identity.Samples -Pre

    Then open the _layout view for example, all the HTML helpers have a red squiggly. It will take 2 minutes or less.

    I put @using System.Web.Optimization or something like that on the view and it fixed the issue. I am not crazy about the solution. I would have to put that in every view. I am thinking that maybe because the samples are in Alpha there might be a bug?

    Bu the way, I appreciate your help, this is really frustrating.


  • John Atten

    John AttenJohn Atten

    Author Reply

    See what happens when you clone from my github.

    I'm gonna ask a silly question, but did you try just running the project anyway? Intellisense and such gets a little flakey with the views.


  • Cindy

    CindyCindy

    Author Reply

    I created a project from an empty template. Then I opened the Package Manager console and typed: Install-Package Microsoft.AspNet.Identity.Samples -Pre

    Then I open the _layout view for example, all the HTML helpers have a red squiggly.

    I am running VS 2013.

    Can someone try to replicate this as well? Is this a bug in the Samples?


  • John Atten

    John AttenJohn Atten

    Author Reply

    Did you close the code from Github, or build out yourself by following the article? Try cloning the example projecet from teh link to my github repo and see if you get the same error.


  • Cindy

    CindyCindy

    Author Reply

    Hi just create a new project using samples and I get the following error: the name {} does not exist for my razor expression. Anyone else having this issues in their views and layouts?


  • Victor

    VictorVictor

    Author Reply

    Excelent article jatten.

    But, exist something similar like a sample project for a Web project and the use of users and roles? Because this is for MVC :(

    Many thanks.


  • John Atten

    John AttenJohn Atten

    Author Reply

    @Curl Funke –

    Updated Article, with many thanks!


  • John Atten

    John AttenJohn Atten

    Author Reply

    Ahhh… Thanks for pointing that out! I'll need to update the article…


  • Carl Funke

    Carl FunkeCarl Funke

    Author Reply

    The UserAdminController Edit (Get) method needs to populate the EditUserViewModel with the address information in the return View statement

    return View(new EditUserViewModel()
    {
    Id = user.Id,
    Email = user.Email,
    Address = user.Address,
    City = user.City,
    State = user.State,
    PostalCode = user.PostalCode,
    RolesList = RoleManager.Roles.ToList().Select(x => new SelectListItem()
    {
    Selected = userRoles.Contains(x.Name),
    Text = x.Name,
    Value = x.Name
    })


  • XIV-Admin

    XIV-AdminXIV-Admin

    Author Reply

    Thanks everyone!

    @Gareth – No worries – sorry it took so long to get back to this. More is coming up.


  • Kelly Kimble

    Nice work, well done.


  • Gareth

    GarethGareth

    Author Reply

    Thanks for the article and sorry for badgering you to do it!!

    Pretty much what I came up with apart from a few code re-factoring. Excellent resource you have here!!


  • sami

    samisami

    Author Reply

    Thank you very much. This was very informative.