Click here to Skip to main content
15,881,588 members
Articles / Web Development / HTML

ASP.NET MVC 5: Extending ASP.NET Identity 2.0 Roles and Implementation of Role Based Authorization

Rate me:
Please Sign up or sign in to vote.
4.30/5 (22 votes)
22 May 2015CPOL2 min read 197.9K   10.1K   56   45
Implementation of basic role based authorization in ASP.NET MVC5 with ASP.NET Identity 2.0

Introduction

As many people already discovered that ASP.NET Identity 2.0 does not work with the same code as they have done for Identity 1.0 just like me, in this article, I tried to implement a simple role based authorization with ASP.NET MVC 5 and Identity 2.0.

Disclaimer

Most of the codes I have used are taken from John Atten's wonderful article Extending Identity Accounts and Implementing Role-Based Authentication in ASP.NET MVC 5. So, if you find similarities in codes and the title, it is not a coincidence. :)

Straight to the Business

I have not become a charming writer yet. So let's get down straight to the business.

Creating the Project

That's the easiest part.

  1. Go to File >> New Project
  2. Select Web >> ASP.NET Web Application
  3. Type in the name of the project: ASPNetMVCExtendingIdentity2Roles
  4. Click on OK.
  5. Select MVC as the template.
  6. Click on OK.

Voyla!!! You are done with the project creation. Now if you have looked at the codes attached with this article, you might have not noticed I did a bit of juggle with the files and folders. But I am pretty sure you will know what to do if you get a build error.

Domain Models

Models are the most important things in MVC. I will start with the domain models first.

ApplicationRole

Create a new class in the Models folder and name it as ApplicationRole. Put the following code in the file.

C#
using Microsoft.AspNet.Identity.EntityFramework;

namespace ASPNetMVCExtendingIdentity2Roles.Models
{
    public class ApplicationRole : IdentityRole
    {
        public ApplicationRole() : base() { }

        public ApplicationRole(string name, string description)
            : base(name)
        {
            this.Description = description;
        }

        public virtual string Description { get; set; }
    }
}

ApplicationUserRole

Time for another class, ApplicationUserRole this time which will relate the ApplicationUser to ApplicationRole.

C#
using Microsoft.AspNet.Identity.EntityFramework;

namespace ASPNetMVCExtendingIdentity2Roles.Models
{
    public class ApplicationUserRole : IdentityUserRole
    {
        public ApplicationUserRole()
            : base()
        { }

        public ApplicationRole Role { get; set; }
    }
}

ApplicationUser

We are almost there. We need just a little finishing touch to the already existing ApplicationUser class. Modify the class with the following code.

C#
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks;

namespace ASPNetMVCExtendingIdentity2Roles.Models
{
    // You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more.
    public class ApplicationUser : IdentityUser
    {
        public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
        {
            // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
            var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
            // Add custom user claims here
            return userIdentity;
        }

        public ICollection<ApplicationUserRole> UserRoles { get; set; }
    }
}

 

DbContext

Aah!!! We have reached to the most exciting part of the article, the DbContext. DbContext is the glue what sticks your application to the database. It should be well taken care of. My one is a bit clumsy though. Why don't you try to make yours beautiful? I included my DbContext initializer, my seeding mechanism and some business functionalities as well in this class, which is definitely not a good practice. But we are here to solve the Identity problem, not for learning software development best practice, are we? So, here is my code.

C#
using ASPNetMVCExtendingIdentity2Roles.Models;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;

namespace ASPNetMVCExtendingIdentity2Roles.Context
{
    public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
    {
        public DbSet<ApplicationRole> Roles { get; set; }
        public ApplicationDbContext()
            : base("DefaultConnection", throwIfV1Schema: false)
        {
        }

        public static ApplicationDbContext Create()
        {
            return new ApplicationDbContext();
        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            if (modelBuilder == null)
            {
                throw new ArgumentNullException("ModelBuilder is NULL");
            }

            base.OnModelCreating(modelBuilder);

            //Defining the keys and relations
            modelBuilder.Entity<ApplicationUser>().ToTable("AspNetUsers");
            modelBuilder.Entity<ApplicationRole>().HasKey<string>(r => r.Id).ToTable("AspNetRoles");
            modelBuilder.Entity<ApplicationUser>().HasMany<ApplicationUserRole>((ApplicationUser u) => u.UserRoles);            
            modelBuilder.Entity<ApplicationUserRole>().HasKey(r => new { UserId = r.UserId, RoleId = r.RoleId }).ToTable("AspNetUserRoles");            
        }

        public bool Seed(ApplicationDbContext context)
        {
#if DEBUG
            bool success = false;

            ApplicationRoleManager _roleManager = new ApplicationRoleManager(new RoleStore<ApplicationRole>(context));
            
            success = this.CreateRole(_roleManager, "Admin", "Global Access");
            if (!success == true) return success;

            success = this.CreateRole(_roleManager, "CanEdit", "Edit existing records");
            if (!success == true) return success;

            success = this.CreateRole(_roleManager, "User", "Restricted to business domain activity");
            if (!success) return success;

            // Create my debug (testing) objects here

            ApplicationUserManager userManager = new ApplicationUserManager(new UserStore<ApplicationUser>(context));

            ApplicationUser user = new ApplicationUser();
            PasswordHasher passwordHasher = new PasswordHasher();

            user.UserName = "youremail@testemail.com";
            user.Email = "youremail@testemail.com";

            IdentityResult result = userManager.Create(user, "Pass@123");

            success = this.AddUserToRole(userManager, user.Id, "Admin");
            if (!success) return success;

            success = this.AddUserToRole(userManager, user.Id, "CanEdit");
            if (!success) return success;

            success = this.AddUserToRole(userManager, user.Id, "User");
            if (!success) return success;

            return success;
#endif
        }

        public bool RoleExists(ApplicationRoleManager roleManager, string name)
        {
            return roleManager.RoleExists(name);
        }

        public bool CreateRole(ApplicationRoleManager _roleManager, string name, string description = "")
        {            
            var idResult = _roleManager.Create<ApplicationRole, string>(new ApplicationRole(name, description));
            return idResult.Succeeded;
        }

        public bool AddUserToRole(ApplicationUserManager _userManager, string userId, string roleName)
        {
            var idResult = _userManager.AddToRole(userId, roleName);
            return idResult.Succeeded;
        }

        public void ClearUserRoles(ApplicationUserManager userManager, string userId)
        {
            var user = userManager.FindById(userId);
            var currentRoles = new List<IdentityUserRole>();

            currentRoles.AddRange(user.UserRoles);
            foreach (ApplicationUserRole role in currentRoles)
            {
                userManager.RemoveFromRole(userId, role.Role.Name);
            }
        }

        public void RemoveFromRole(ApplicationUserManager userManager, string userId, string roleName)
        {
            userManager.RemoveFromRole(userId, roleName);
        }

        public void DeleteRole(ApplicationDbContext context, ApplicationUserManager userManager, string roleId)
        {
            var roleUsers = context.Users.Where(u => u.UserRoles.Any(r => r.RoleId == roleId));
            var role = context.Roles.Find(roleId);

            foreach (var user in roleUsers)
            {
                this.RemoveFromRole(userManager, user.Id, role.Name);
            }
            context.Roles.Remove(role);
            context.SaveChanges();
        }

        /// <summary>
        /// Context Initializer
        /// </summary>
        public class DropCreateAlwaysInitializer : DropCreateDatabaseAlways<ApplicationDbContext>
        {
            protected override void Seed(ApplicationDbContext context)
            {
                context.Seed(context);

                base.Seed(context);
            }
        }
    }    
}

Don't forget to change the username and password in the seeding section. To initialize the DbContext the Global.asax file needs to be updated.

C#
using ASPNetMVCExtendingIdentity2Roles.Context;
using System.Data.Entity;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;

namespace ASPNetMVCExtendingIdentity2Roles
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);

#if DEBUG
            Database.SetInitializer<ApplicationDbContext>(new ApplicationDbContext.DropCreateAlwaysInitializer());
#endif
        }
    }
}

ViewModels

ViewModel is a class that contains the data required for a View. They are not a mandatory part of the MVC pattern, but they make it easy to implement a view supplying the necessary data it requires. 

RoleViewModels

C#
using System.ComponentModel.DataAnnotations;

namespace ASPNetMVCExtendingIdentity2Roles.Models
{
    public class RoleViewModel
    {
        public string RoleName { get; set; }
        public string Description { get; set; }

        public RoleViewModel() { }
        public RoleViewModel(ApplicationRole role)
        {
            this.RoleName = role.Name;
            this.Description = role.Description;
        }
    }

    public class SelectRoleEditorViewModel
    {
        public SelectRoleEditorViewModel() { }

        // Update this to accept an argument of type ApplicationRole:
        public SelectRoleEditorViewModel(ApplicationRole role)
        {
            this.RoleName = role.Name;

            // Assign the new Descrption property:
            this.Description = role.Description;
        }

        public bool Selected { get; set; }

        [Required]
        public string RoleName { get; set; }

        // Add the new Description property:
        public string Description { get; set; }
    }

    public class EditRoleViewModel
    {
        public string OriginalRoleName { get; set; }
        public string RoleName { get; set; }
        public string Description { get; set; }

        public EditRoleViewModel() { }
        public EditRoleViewModel(ApplicationRole role)
        {
            this.OriginalRoleName = role.Name;
            this.RoleName = role.Name;
            this.Description = role.Description;
        }
    }
}

Controllers

RolesController

Create the RolesController and put the following codes in.

C#
using ASPNetMVCExtendingIdentity2Roles.Context;
using ASPNetMVCExtendingIdentity2Roles.Models;
using Microsoft.AspNet.Identity.EntityFramework;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Net;
using System.Web.Mvc;

namespace ASPNetMVCExtendingIdentity2Roles.Controllers
{
    public class RolesController : Controller
    {
        private ApplicationDbContext _db = new ApplicationDbContext();

        public ActionResult Index()
        {
            var rolesList = new List<RoleViewModel>();
            foreach (var role in _db.Roles)
            {
                var roleModel = new RoleViewModel(role);
                rolesList.Add(roleModel);
            }
            return View(rolesList);
        }

        [Authorize(Roles = "Admin")]
        public ActionResult Create(string message = "")
        {
            ViewBag.Message = message;
            return View();
        }

        [HttpPost]
        [Authorize(Roles = "Admin")]
        public ActionResult Create([Bind(Include =
            "RoleName,Description")]RoleViewModel model)
        {
            string message = "That role name has already been used";
            if (ModelState.IsValid)
            {
                var role = new ApplicationRole(model.RoleName, model.Description);
                ApplicationRoleManager _roleManager = new ApplicationRoleManager(new RoleStore<ApplicationRole>(_db));
                if (_db.RoleExists(_roleManager, model.RoleName))
                {
                    return View(message);
                }
                else
                {
                    _db.CreateRole(_roleManager, model.RoleName, model.Description);
                    return RedirectToAction("Index", "Roles");
                }
            }
            return View();
        }

        [Authorize(Roles = "Admin")]
        public ActionResult Edit(string id)
        {
            // It's actually the Role.Name tucked into the id param:
            var role = _db.Roles.First(r => r.Name == id);
            var roleModel = new EditRoleViewModel(role);
            return View(roleModel);
        }

        [HttpPost]
        [Authorize(Roles = "Admin")]
        public ActionResult Edit([Bind(Include =
            "RoleName,OriginalRoleName,Description")] EditRoleViewModel model)
        {
            if (ModelState.IsValid)
            {
                var role = _db.Roles.First(r => r.Name == model.OriginalRoleName);
                role.Name = model.RoleName;
                role.Description = model.Description;
                _db.Entry(role).State = EntityState.Modified;
                _db.SaveChanges();
                return RedirectToAction("Index");
            }
            return View(model);
        }

        [Authorize(Roles = "Admin")]
        public ActionResult Delete(string id)
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }
            var role = _db.Roles.First(r => r.Name == id);
            var model = new RoleViewModel(role);
            if (role == null)
            {
                return HttpNotFound();
            }
            return View(model);
        }

        [Authorize(Roles = "Admin")]
        [HttpPost, ActionName("Delete")]
        public ActionResult DeleteConfirmed(string id)
        {
            var role = _db.Roles.First(r => r.Name == id);
            ApplicationUserManager userManager = new ApplicationUserManager(new UserStore<ApplicationUser>(_db));
            _db.DeleteRole(_db, userManager, role.Id);
            return RedirectToAction("Index");
        }
    }
}

Views

Right click on the Actions in the RolesController and create the respective views.

Create

HTML
@model ASPNetMVCExtendingIdentity2Roles.Models.RoleViewModel

@{
    ViewBag.Title = "Create";
}

<h2>Create Role</h2>

@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()

    <div class="form-horizontal">
        <h4>RolesViewModel</h4>
        <hr />
        @Html.ValidationSummary(true)

        @if (ViewBag.Message != "")
        {
            <p style="color: red">ViewBag.Message</p>
        }
        <div class="form-group">
            @Html.LabelFor(model => model.RoleName, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.RoleName)
                @Html.ValidationMessageFor(model => model.RoleName)
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Description, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Description)
                @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")
}

Delete

HTML
@model ASPNetMVCExtendingIdentity2Roles.Models.RoleViewModel

@{
    ViewBag.Title = "Delete";
}

<h2>Delete</h2>

<h3>Are you sure you want to delete this?</h3>
<div>
    <h4>RolesViewModel</h4>
    <hr />
    <dl class="dl-horizontal">
        <dt>
            @Html.DisplayNameFor(model => model.RoleName)
        </dt>

        <dd>
            @Html.DisplayFor(model => model.RoleName)
        </dd>

        <dt>
            @Html.DisplayNameFor(model => model.Description)
        </dt>

        <dd>
            @Html.DisplayFor(model => model.Description)
        </dd>

    </dl>

    @using (Html.BeginForm())
    {
        @Html.AntiForgeryToken()

        <div class="form-actions no-color">
            <input type="submit" value="Delete" class="btn btn-default" /> |
            @Html.ActionLink("Back to List", "Index")
        </div>
    }
</div>

Edit

HTML
@model ASPNetMVCExtendingIdentity2Roles.Models.EditRoleViewModel

@{
    ViewBag.Title = "Edit";
}

<h2>Edit</h2>

@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()

    <div class="form-horizontal">
        <h4>EditRolesViewModel</h4>
        <hr />
        @Html.ValidationSummary(true)

        @*Hide the original name away for later:*@
        @Html.HiddenFor(model => model.OriginalRoleName)

        <div class="form-group">
            @Html.LabelFor(model => model.RoleName, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.RoleName)
                @Html.ValidationMessageFor(model => model.RoleName)
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Description, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Description)
                @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")
}

Index

HTML
@model IEnumerable<ASPNetMVCExtendingIdentity2Roles.Models.RoleViewModel>

@{
    ViewBag.Title = "Application Roles";
}

<h2>Application Roles</h2>

<p>
    @Html.ActionLink("Create New", "Create")
</p>
<table class="table">
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.RoleName)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Description)
        </th>
        <th></th>
    </tr>

    @foreach (var item in Model)
    {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.RoleName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Description)
            </td>
            <td>
                @Html.ActionLink("Edit", "Edit", new { id = item.RoleName }) |
                @Html.ActionLink("Delete", "Delete", new { id = item.RoleName })
            </td>
        </tr>
    }

</table>

Conclusion

That is all. Run your application. Login with the username and password you used in seeding, hit on the Roles link. Thank you for reading this far.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Australia Australia
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionApplicationRoleManager is missing Pin
Member 1299209412-Feb-17 8:24
Member 1299209412-Feb-17 8:24 
QuestionHow to retrieve a user's roles Pin
Kim Turok16-Jan-17 8:27
Kim Turok16-Jan-17 8:27 
GeneralMy vote of 5 Pin
khurram ali lashari14-Oct-16 7:16
professionalkhurram ali lashari14-Oct-16 7:16 
QuestionWhat to disabel in ApplicationDbContext that stops recreating a tables in SQL? Pin
Niyazi Toros7-Jun-16 23:25
Niyazi Toros7-Jun-16 23:25 
AnswerRe: What to disabel in ApplicationDbContext that stops recreating a tables in SQL? Pin
Niyazi Toros8-Jun-16 1:32
Niyazi Toros8-Jun-16 1:32 
GeneralRe: What to disabel in ApplicationDbContext that stops recreating a tables in SQL? Pin
debashishPaul8-Jun-16 1:53
professionaldebashishPaul8-Jun-16 1:53 
QuestionHow can I add User to Role? Pin
Niyazi Toros7-Jun-16 22:55
Niyazi Toros7-Jun-16 22:55 
AnswerRe: How can I add User to Role? Pin
debashishPaul8-Jun-16 1:52
professionaldebashishPaul8-Jun-16 1:52 
GeneralRe: How can I add User to Role? Pin
Niyazi Toros8-Jun-16 2:54
Niyazi Toros8-Jun-16 2:54 
GeneralRe: How can I add User to Role? Pin
debashishPaul8-Jun-16 14:06
professionaldebashishPaul8-Jun-16 14:06 
GeneralRe: How can I add User to Role? Pin
Niyazi Toros8-Jun-16 18:20
Niyazi Toros8-Jun-16 18:20 
QuestionHow can I use Role to activate the Menus? Pin
Niyazi Toros7-Jun-16 22:07
Niyazi Toros7-Jun-16 22:07 
AnswerRe: How can I use Role to activate the Menus? Pin
debashishPaul8-Jun-16 1:20
professionaldebashishPaul8-Jun-16 1:20 
GeneralRe: How can I use Role to activate the Menus? Pin
Niyazi Toros8-Jun-16 1:29
Niyazi Toros8-Jun-16 1:29 
QuestionHow to login the application role's view Pin
saif1234567896-Oct-15 20:24
saif1234567896-Oct-15 20:24 
AnswerRe: How to login the application role's view Pin
debashishPaul8-Jun-16 1:11
professionaldebashishPaul8-Jun-16 1:11 
SuggestionApplicationRoleManager code not showed in the article Pin
Foyzul Karim11-Aug-15 19:20
professionalFoyzul Karim11-Aug-15 19:20 
GeneralRe: ApplicationRoleManager code not showed in the article Pin
Member 1157664716-Sep-15 13:40
Member 1157664716-Sep-15 13:40 
GeneralRe: ApplicationRoleManager code not showed in the article Pin
Member 1199850020-Sep-15 13:31
Member 1199850020-Sep-15 13:31 
GeneralRe: ApplicationRoleManager code not showed in the article Pin
bbirajdar27-Oct-16 2:51
bbirajdar27-Oct-16 2:51 
QuestionAuthorize not working (Mvc 6 and identity 3.0) Pin
marcosph9-Jul-15 3:34
marcosph9-Jul-15 3:34 
QuestionAuthorization not working in ASP.NET MVC 6 Pin
marcosph5-Jul-15 7:42
marcosph5-Jul-15 7:42 
QuestionIssue with Adding ICollection to ApplicationUserRole Pin
Member 1175970029-Jun-15 5:39
Member 1175970029-Jun-15 5:39 
QuestionMissing... Pin
Nelek22-Jun-15 10:22
protectorNelek22-Jun-15 10:22 
QuestionAlready exists? Pin
Member 1177892719-Jun-15 13:54
Member 1177892719-Jun-15 13:54 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.