At some point you probably realized that users will need to log into your website. Identifying who your users are is known as authentication. You may also need the added ability to know what resources each user is allowed to access. This is accomplished in the form of roles using authorization. This functionality is built into the MVC framework right out of the box in the form of the Login control and the ASP.NET default membership and role providers. If you're like me you may want more control over what data is stored for a user and how it is stored. Towards this end we'll be implementing our own method of authorizing and authenticating users in this post. There are several different ways to do this depending on the level of control needed. We'll begin by doing the bare minimum and we'll add on more complexity and specialization as we go. Feel free to implement the parts you need and use the out-of-the-box features that do not need custom implementations. I'll try to keep these sections separate from one another to make following along easier.
For the purposes of this tutorial we'll use the following scenario: Our website supports a local bank. There will be two available roles for users: Member and Administrator. When storing customer information we will be interested in thier name, username, email, password and their role of course.
Setup
In order to follow along with this tutorial you'll need ASP.NET MVC 3 and Entity Framework 4.1. Both of these can be downloaded from Microsoft's website. An even easier way to accomplish this is to download and install ASP.NET MVC 3 which comes with NuGet (a Visual Studio package management plug-in). In order to load/install Entity Framework 4.1 using NuGet simply open Visual Studio 2010 and navigate to Tools -> Library Package Manager -> Package Manager Console. Once the console is open just type "install-package Entityframework". This will install Entity Framework 4.1 and add the reference to your open web application.
The starting point for this tutorial will be a new ASP.NET MVC 3 Web Application. I chose the Internet Application template which gives us an account controller and form authentication right away. I'll also be using Razor for the view engine.
Creating the User & Role Tables
When a user registers with your website we'll need a place to store this information. Since we'll no longer be using the out-of-the-box ASP.NET default role provider we'll need to create a table to contain this information ourselves. The schema and type of database you decide to use for these tables is completely up to you. That's the main reason for extending these providers to begin with, flexibility.
For this tutorial I'm creating a brand new SQL Server database under my project named LocalBankDB.mdf. However, you could use Oracle, SQLite, or any other database which can be connected to using ADO.NET. The process of creating these tables and thier relationship(s) is out of scope for this tutorial, but I'll be using the schema shown in this diagram:
As you can see from the diagram every user has 1 and only 1 role while a role can be assigned to any number of users. Besides this relationship we'll be storing the users name, user name, password (will be encrypted/hashed), and their email address.
Creating the User & Role Models
We'll be using EntityFramework 4.1 to create a model from our database. To begin right click on your Models folder and select Add -> New Item... Select ADO.NET Entity Data Model from the Data template and name our new model LocalBankModel.edmx then press Add. Since we have already defined our database we'll select the option to generate our model from our database.
Next you'll need to specify the database to use. In my case it's a local SQL Server database named LocalBankDB.mdf but you can use the New Connection option to connect to other types of databases if needed. Notice that VS has an option to create a connection string for us in the Web.Config file. This makes things much simpler so make sure this option is checked. I'll be naming the connection LocalBankEntities.
Select the User and Role tables and click Finish. This will create our LocalBankEntities OjbectContext which we can use to query the database and supports all CRUD operations. This will also create a Role and User EntityObject which encapsulates the structure of our database tables. A new entry will also be added to our Web.Config file, but we'll get to that in just a bit.
Creating a Repository (optional step)
I'm a big fan of the repository pattern which allows us to write friendly methods to encapsulate working with our entities. This allows our controllers to work with our custom classes instead of directly calling entity objects. This allows us to rebuild our database and our model layer without affecting our controller classes. To begin we'll create a new class called LocalBankRepository which contains a private instance of the LocalBankEntities object and has a number of methods for accessing basic CRUD scenarios.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Security;
namespace LocalBank.Models
{
public class LocalBankRepository
{
#region Variables
private LocalBankEntities entities = new LocalBankEntities();
private const string MissingRole = "Role does not exist";
private const string MissingUser = "User does not exist";
private const string TooManyUser = "User already exists";
private const string TooManyRole = "Role already exists";
private const string AssignedRole = "Cannot delete a role with assigned users";
#endregion
#region Properties
public int NumberOfUsers
{
get
{
return this.entities.Users.Count();
}
}
public int NumberOfRoles
{
get
{
return this.entities.Roles.Count();
}
}
#endregion
#region Constructors
public LocalBankRepository()
{
this.entities = new LocalBankEntities();
}
#endregion
#region Query Methods
public IQueryable<User> GetAllUsers()
{
return from user in entities.Users
orderby user.UserName
select user;
}
public User GetUser(int id)
{
return entities.Users.SingleOrDefault(user => user.ID == id);
}
public User GetUser(string userName)
{
return entities.Users.SingleOrDefault(user => user.UserName == userName);
}
public IQueryable<User> GetUsersForRole(string roleName)
{
return GetUsersForRole(GetRole(roleName));
}
public IQueryable<User> GetUsersForRole(int id)
{
return GetUsersForRole(GetRole(id));
}
public IQueryable<User> GetUsersForRole(Role role)
{
if (!RoleExists(role))
throw new ArgumentException(MissingRole);
return from user in entities.Users
where user.RoleID == role.ID
orderby user.UserName
select user;
}
public IQueryable<Role> GetAllRoles()
{
return from role in entities.Roles
orderby role.Name
select role;
}
public Role GetRole(int id)
{
return entities.Roles.SingleOrDefault(role => role.ID == id);
}
public Role GetRole(string name)
{
return entities.Roles.SingleOrDefault(role => role.Name == name);
}
public Role GetRoleForUser(string userName)
{
return GetRoleForUser(GetUser(userName));
}
public Role GetRoleForUser(int id)
{
return GetRoleForUser(GetUser(id));
}
public Role GetRoleForUser(User user)
{
if (!UserExists(user))
throw new ArgumentException(MissingUser);
return user.Role;
}
#endregion
#region Insert/Delete
private void AddUser(User user)
{
if (UserExists(user))
throw new ArgumentException(TooManyUser);
entities.Users.AddObject(user);
}
public void CreateUser(string username, string name, string password, string email, string roleName)
{
Role role = GetRole(roleName);
if (string.IsNullOrEmpty(username.Trim()))
throw new ArgumentException("The user name provided is invalid. Please check the value and try again.");
if (string.IsNullOrEmpty(name.Trim()))
throw new ArgumentException("The name provided is invalid. Please check the value and try again.");
if (string.IsNullOrEmpty(password.Trim()))
throw new ArgumentException("The password provided is invalid. Please enter a valid password value.");
if (string.IsNullOrEmpty(email.Trim()))
throw new ArgumentException("The e-mail address provided is invalid. Please check the value and try again.");
if (!RoleExists(role))
throw new ArgumentException("The role selected for this user does not exist! Contact an administrator!");
if (this.entities.Users.Any(user => user.UserName == username))
throw new ArgumentException("Username already exists. Please enter a different user name.");
User newUser = new User()
{
UserName = username,
Name = name,
Password = FormsAuthentication.HashPasswordForStoringInConfigFile(password.Trim(), "md5"),
Email = email,
RoleID = role.ID
};
try
{
AddUser(newUser);
}
catch (ArgumentException ae)
{
throw ae;
}
catch (Exception e)
{
throw new ArgumentException("The authentication provider returned an error. Please verify your entry and try again. " +
"If the problem persists, please contact your system administrator.");
}
// Immediately persist the user data
Save();
}
public void DeleteUser(User user)
{
if (!UserExists(user))
throw new ArgumentException(MissingUser);
entities.Users.DeleteObject(user);
}
public void DeleteUser(string userName)
{
DeleteUser(GetUser(userName));
}
public void AddRole(Role role)
{
if (RoleExists(role))
throw new ArgumentException(TooManyRole);
entities.Roles.AddObject(role);
}
public void AddRole(string roleName)
{
Role role = new Role()
{
Name = roleName
};
AddRole(role);
}
public void DeleteRole(Role role)
{
if (!RoleExists(role))
throw new ArgumentException(MissingRole);
if (GetUsersForRole(role).Count() > 0)
throw new ArgumentException(AssignedRole);
entities.Roles.DeleteObject(role);
}
public void DeleteRole(string roleName)
{
DeleteRole(GetRole(roleName));
}
#endregion
#region Persistence
public void Save()
{
entities.SaveChanges();
}
#endregion
#region Helper Methods
public bool UserExists(User user)
{
if (user == null)
return false;
return (entities.Users.SingleOrDefault(u => u.ID == user.ID || u.UserName == user.UserName) != null);
}
public bool RoleExists(Role role)
{
if (role == null)
return false;
return (entities.Roles.SingleOrDefault(r => r.ID == role.ID || r.Name == role.Name) != null);
}
#endregion
}
}
There's quite a bit of code there but the 2 methods to pay attention to are the AddUser and CreateUser. I chose to keep AddUser private in order to force controllers to call my CreateUser method which validates the user, encrypts the users password, and persists the data to the database. There are many ways of accomplishing this so feel free to expand on or write your own repository for your user and role management.
Creating a Custom RoleProvider
Finally we get to the meat of this topic, creating our custom RoleProvider. This is actually quite simple. Create a new class called LocalBankRoleProvider which extends System.Web.Security.RoleProvider. Right-click on RoleProvider and choose to Implement Abstract Class.
Doing this will toss lots of method stubs into your class which throw a NotImplementedException. This is fine, we'll only be implementing 2 of these methods: IsUserInRole and GetRolesForUser. Also, make sure to add a local private variable to access our repository.
private LocalBankRepository repository;
public LocalBankRoleProvider()
{
this.repository = new LocalBankRepository();
}
public override bool IsUserInRole(string userName, string roleName)
{
User user = repository.GetUser(userName);
Role role = repository.GetRole(roleName);
if (!repository.UserExists(user))
return false;
if (!repository.RoleExists(role))
return false;
return user.Role.Name == role.Name;
}
public override string[] GetRolesForUser(string userName)
{
Role role = this.repository.GetRoleForUser(userName);
if (!this.repository.RoleExists(role))
return new string[] { string.Empty };
return new string[] { role.Name };
}
Creating a Custom MembershipProvider
The next step is to create our custom MembershipProvider. Create a new class LocalBankMembershipProvider which extends System.Web.Security.MembershipProvider. Just like the RoleProvider right-click on MembershipProvider and choose to Implement Abstract Class. Again you will get many methods which throw a NotImplementedException. We'll be implementing 2 of these methods and 1 property: ValidateUser, ChangePassword and MinRequiredPasswordLength.
private LocalBankRepository repository;
public override int MinRequiredPasswordLength
{
get
{
return 6;
}
}
public LocalBankMembershipProvider()
{
this.repository = new LocalBankRepository();
}
public override bool ValidateUser(string username, string password)
{
if (string.IsNullOrEmpty(password.Trim()) || string.IsNullOrEmpty(username.Trim()))
return false;
string hash = FormsAuthentication.HashPasswordForStoringInConfigFile(password.Trim(), "md5");
return this.repository.GetAllUsers().Any(user => (user.UserName == username.Trim()) && (user.Password == hash));
}
public void CreateUser(string username, string fullName, string password, string email, string roleName)
{
this.repository.CreateUser(username, fullName, password, email, roleName);
}
public override bool ChangePassword(string username, string oldPassword, string newPassword)
{
if (!ValidateUser(username, oldPassword) || string.IsNullOrEmpty(newPassword.Trim()))
return false;
User user = repository.GetUser(username);
string hash = FormsAuthentication.HashPasswordForStoringInConfigFile(newPassword.Trim(), "md5");
user.Password = hash;
repository.Save();
return true;
}
Notice that I didn't use the CreateUser method that comes with the MembershipProvider, I overloaded it so it will accept just the fields our database is interested in and then I passed the workload off to the repository.
Tying our Custom Providers into MVC
We've created our custom providers so the next step is to configure our application to use these instead of the default ones that ship with ASP.NET. Open your Web.Config file which exists at your project root. Notice that the connectionStrings section should contain 2 entries: ApplicationServices (the default connection string) and LocalBankEntities (the one that was added when we created our model). We will no longer be using ApplicationServices so you can delete this entry. First let's modify our membership provider to use our custom provider. Replace the membership section with the following code.
<membership defaultProvider="LocalBankMembershipProvider">
<providers>
<clear />
<add name="LocalBankMembershipProvider" type="LocalBank.Helpers.LocalBankMembershipProvider, LocalBank" connectionStringName="LocalBankEntities" />
</providers>
</membership>
First you must specify a defaultProvider which matches the name of the class which extends the MembershipProvider, in our case this is the LocalBankMembershipProvider. When specifying the type you pass the fully qualified path to your membership class followed by a comma and the name of the assembly it is contained in (aka the project name in most cases). And last but not least we need to specify where the data exists for authenticating membership. We'll be using the LocalBankEntities connection string which was added when we created our model. Next let's modify our roleManager to use our custom provider. Replace the roleManager section with the following code.
<roleManager defaultProvider="LocalBankRoleProvider" enabled="true" cacheRolesInCookie="true">
<providers>
<clear />
<add name="LocalBankRoleProvider" type="LocalBank.Helpers.LocalBankRoleProvider, LocalBank" connectionStringName="LocalBankEntities" />
</providers>
</roleManager>
This is nearly identical to our membership provider except that we need to specify that the role manager is enabled and that we would like to cache the users role in a cookie. This allows role management to check the cookie for role verification before calling the role provider. By default the roles are encrypted inside the cookie and verification is made to make sure they have not been altered. To modify this behavior you can use the CookieProtectionValue property, but this won't be nessesary for this project.
Modifying the AccountController to use our new Providers
By default MVC 3 uses the System.Web.Security.Membership class to authenticate users which we'll need to change in order to use our new membership provider. Let's start by declaring an instance of both providers in our AccountController.
public LocalBankMembershipProvider MembershipService { get; set; }
public LocalBankRoleProvider AuthorizationService { get; set; }
protected override void Initialize(RequestContext requestContext)
{
if (MembershipService == null)
MembershipService = new LocalBankMembershipProvider();
if (AuthorizationService == null)
AuthorizationService = new LocalBankRoleProvider();
base.Initialize(requestContext);
}
The first change we'll need to make is to the LogOn HttpPost action. Replace the usage of the Membership class to use our MembershipService we just created. The code fragment will look something like this:
[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid)
{
if (MembershipService.ValidateUser(model.UserName, model.Password))
{
Next we'll want to change our Register HttpPost action. We don't need to use the MembershipCreateStatus or the Membership classes, instead we will use our own. Here is my implementation.
[HttpPost]
public ActionResult Register(RegisterModel model)
{
if (ModelState.IsValid)
{
// Attempt to register the user
try
{
MembershipService.CreateUser(model.UserName, model.Name, model.Password, model.Email, "Member");
FormsAuthentication.SetAuthCookie(model.UserName, false);
return RedirectToAction("Index", "Home");
}
catch (ArgumentException ae)
{
ModelState.AddModelError("", ae.Message);
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
Notice that I am passing the role name to associate with this user to CreateUser. Because this register method is openly exposed to anyone who wants to sign up on our bank site I hardcoded a default value of "Member". You may have other views which allow other types of roles or even an admin controll which allows the user to select the role for a user. I may post about this if there is any demand.
Finally, we'll want to change the ChangePassword HttpPost action to use our membership provider. This is pretty straight forward since we overrid this method.
[Authorize]
[HttpPost]
public ActionResult ChangePassword(ChangePasswordModel model)
{
if (ModelState.IsValid)
{
if (MembershipService.ChangePassword(User.Identity.Name, model.OldPassword, model.NewPassword))
return RedirectToAction("ChangePasswordSuccess");
else
ModelState.AddModelError("", "The current password is incorrect or the new password is invalid.");
}
// If we got this far, something failed, redisplay form
return View(model);
}
You can also remove the entire "Status Codes" region from your controller as it's no longer being used. You're almost there, but you'll notice that the project doesn't build currently because we are trying to pass model.Name to CreateUser when a user registers and we haven't defined this field yet. This is needed because our original database model requires a name but the built in RegisterModel does not. We'll correct this by making 2 small changes. First, add the following code to our RegisterModel class.
[Required]
[Display(Name = "Name")]
public string Name { get; set; }
We'll also make a small change to our Register view so that a field appears for users to enter thier name. Add the following code to the Register view's form fieldset.
<div class="editor-label">
@Html.LabelFor(m => m.Name)
</div>
<div class="editor-field">
@Html.TextBoxFor(m => m.Name)
@Html.ValidationMessageFor(m => m.Name)
</div>
Run the application and register yourself with the website. Did you get an error message?

We haven't created any roles! This is not really part of this tutorial but just a reminder that you should create your roles either outside of your project using a script or by creating an admin section for your site which will allow you to manage users and roles. Again, I'd be willing to post more about this if there is interest.
Using our Role and Membership Providers
Now that we have created our providers and tied them into our application how do we use them? Well, there are a number of ways to do it depending on where you are and what preferences you have. Here are some short examples of using both in different situations.
Making sure a user is logged in to gain access to a view
The easiest way to accomplish this is to use the Authorize attribute above a controller's action method. For instance, we only want to allow users to change thier password if they are already logged into the site. In order to prevent non-authorized users from reaching the change password view we can restrict access like this:
[Authorize]
public ActionResult ChangePassword()
{
ViewData["PasswordLength"] = MembershipService.MinPasswordLength;
return View();
}
You can also accomplish this manually by checking against the User object like this:
public ActionResult ChangePassword()
{
if (!User.Identity.IsAuthenticated)
return RedirectToAction("LogOn", "Account");
ViewData["PasswordLength"] = MembershipService.MinPasswordLength;
return View();
}
This checks to see if the user is authenticated and if not redirects them to the LogOn view. I would not recommend using this method however; the built in Authorize attribute will automatically redirect non-authorized users to the LogOn view specified in your Web.Config file. Once the user logs in it will automatically redirect them back to the previous page.
Making sure a user is in a particular role to gain access to a view
You may have some views which should only be accessable to users of a particular role. This can also be accomplished using the Authorize attribute like this:
[Authorize(Roles = "Administrator")]
public ActionResult Index()
{
return View();
}
You can accomplish this in code as well using the following method:
public ActionResult Index()
{
if (!User.IsInRole("Administrator"))
return RedirectToAction("LogOn", "Account");
return View();
}
Again, this is not recommended for the same reasons as before.
Checking authentication and roles in a view
I am using the Razor view engine so this may be a bit different than your code but the principles are the same. You may not want to stop a user from accessing a view but you may want to hide/show content based on a users authentication or role. This can be accomplished using code such as this:
<li>@Html.ActionLink("Home", "Index", "Home")</li>
@if (Request.IsAuthenticated)
{
<li>@Html.ActionLink("View Account", "Index", "Account")</li>
if (User.IsInRole("Administrator"))
{
<li>@Html.ActionLink("Admin Console", "Index", "Admin")</li>
}
}
As you can see the Request object will allow us to verify if a user is authenticated and we also have a User object which allows us to check for roles. Notice in this code snippit that anyone visiting the site will get a Home link, authenticated users will also get a View Account link and authenticated administrator users will also see an Admin Console link.
Please leave feedback if this helped. This is my first blog entry so I'm not sure if I was too detailed or not enough. Also, if you find any errors or issues please let me know. I plan to write more entries similar to this one so let me know if there is anything you'd like to see. Thanks for reading!
Full Source: LocalBank.zip (2.87 mb)
Tags: RoleProvider, MembershipProvider, MVC, Entity Framework