Introduction
In this we will discuss about the ASP.NET Roles and Membership API from MVC perspective.
We will try to see how the default Roles and Membership provides can be used for
authentication and authorization in an MVC application. We will also see how we can implement custom
forms authentication in an ASP.NET MVC application.
Background
When we are working on application in which authentication and authorization is a key requirement,
we will find the ASP.NET roles and membership feature very useful. The basic principle and mechanism
for forms authentication in ASP.NET MVC is same as of that of ASP.NET Webforms
. But since we don't have
the server controls with us the way to use it will be a little different from that of the webforms.
Nevertheless, since the core principle behind the Forms authentication is same, I suggest following
articles will provide a quick recap on ASP.NET Forms authentication and how it can be implemented and
customized in WebForms
applications.
Authentication and Authorization
Authentication means validating users. In this step, we verify user credentials to check
whether the person tying to log in is the right one or not.
Authorization on the other hand is keeping track of what the current user is allowed
to see and what should be hidden from him. It is more like keeping a register to what to
show and what not to show to the user.
Whenever a user logs in, he will have to authenticate himself with his credentials.
Once he is authenticated, he will be authorized to see resources/pages of the website.
Mostly these two concepts go together.
Type of Authentications
Before moving ahead, let us first see the two main type of authentications that are used mostly in ASP.NET
applications.
-
Windows authentication: In this mode, the users are authenticated on their Windows username and password.
This method is least recommended in an internet scenario. In an internet scenario,
we should always use "Forms based authentication".
-
Forms based authentication: In this type of authentication, the user will explicitly have to
provide his credentials and these credentials, once verified by the server, will let the user to log in.
We will be discussing the form authentication in details in the rest of the article.
Using the code
The default ASP.NET Roles and Membership classes come in very handy when we want to
provide authentication and authorization in our applications. Using the default membership API will
use the membership database and provide us will all the functionality required to manage the user roles
and membership.
ASP.NET also provides a way to implement custom Roles and Membership to take more granular control over things.
We might still find ourselves in situations where we need to have our own database for tracking users and their roles.
The reasons could be:
-
We have an existing database and we are trying to implement an application using that.
-
The Roles and Membership functionality is overkill for our application.
-
The Roles and Membership functionality is not sufficient for our applications and we need custom data.
So let us first discuss the default membership API and what visual studio provides us out of the box. We
will then move on to taking the full control on authorization and authentication on our hand by implementing
custom forms authentication.
Forms Authentication
To enable forms authentication we need to perform following steps in our application.
-
Configure the application to use Forms Authentication.
-
Create a login page.
-
Whenever a user tries to access the restricted area, push him to the Login page.
-
When the user tries to login, verify his credentials using the database.
-
If the login is successful, keep the username and his Roles in a Session variable to use it further.
-
Create an authentication ticket for the user (an encrypted cookie). We can have this persistent or non persistent.
-
Facilitate the User/Roles extraction using the authentication ticket.
-
Use the user/Roles found in the last step and create a Principal using that so that the
ASP.NET Forms Authentication mechanism can use this data.
Now we will see how the default membership API does all these things for us and how we can
implement all these steps ourselves.
Default Membership API
When we create an MVC internet application. The visual studio project wizard does all these above
mentioned steps for us. it will create a Membership database in the App_data directory of our application
(actually the location depends on the connectionstring
specified in web.config
file).
It will generate the Controller
ode that will check the database for user authentication, create the
authentication cookie and creation of Principal
based on the roles configured in our database.
It will also generate all the view required for authentication. The image below shows the generated Controller
,
Views and the database.
The Roles and user can be configured either from code or from the Web Site Administration Tool(WSAT)
in the same manner as of that in WebForms
application. So let us create a role called "admin" and
2 users "admin" and "user" using WSAT
. The "admin" will be in "admin" role and the "user" will not be in any role.
Now from the applications perspective, we only need to mark the views that need authentication and
authorization and the default membership classes and the generated classed will take care of performing
the authentication and authorization.
Let us say that we want only authentication users to be able to view the Home/Index
page. And only the
users in "Admin" role can access the Home/About
page. To do this we need to decorate the respective action in controller with the authorize attribute as:
public class HomeController : Controller
{
[Authorize]
public ActionResult Index()
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
return View();
}
[Authorize(Roles="Admin")]
public ActionResult About()
{
return View();
}
}
And that is all that is required to require authentication and authorization if we are using default Membership
API and the visual studio generated code for authentication and authorization.
We can do some level of customization in the AccountController
class if we need some added functionality.
Note: The article does not contain a sample for the default membership usage because it is just the matter
of creating a new MVC 3 internet application and all the code will be generated by visual studio itself.
It is highly recommended to look at the AccountController
class to see how it is performing various operations.
Custom Forms Authentication
Now if we don't want to use the default membership API and the visual studio generated code then we can choose to
implement our own authentication and authorization mechanism. To do this we will have take care of implementing all
the steps required for forms authentication that we discussed earlier in the article. So let us create and empty MVC 3
application and see how we can implement custom forms authentication.
Configuring Forms Authentication
Now the first thing that we need to do is to configure the application to use the Forms authentication.
This can be done in the web.config file.
<authentication mode="Forms">
<forms loginUrl="~/Account/Login" timeout="2880" />
</authentication>
Preparing the user Database
let us now create a small database that we will use to perform authentication. This database contains only
two users like our old earlier example. "admin" and "user". "admin" is in "admin" role and "user" is not in any role.
Note: The database is neither optimized nor normalized as that was not the main intent of this article.
A real world example of database will be more optimized and perhaps more complex.
The passwords will not be in clear text for sure, They will either be encrypted or "hashed & salted".
Now to perform data access let us use entity framework so that we don't have to write all the boilerplate code
required to create our model and data access logic. The generated entity for our database will look like:
Creating the Controllers and Views
let us now go ahead and create a controller that will take care of the authentication logic. We will create the functionality for Login
and Logout
but other functionality are user creation and password change can be easily
implemented on same lines(its the matter of validating the user Model and performing CRUD operations on the table
after encryption or hashing and salting).
Here is our controller with login
and logout
action:
public ActionResult Login()
{
return View();
}
[HttpPost]
public ActionResult Login(User model, string returnUrl)
{
if (ModelState.IsValid)
{
using (userDbEntities entities = new userDbEntities())
{
string username = model.username;
string password = model.password;
bool userValid = entities.Users.Any(user => user.username == username && user.password == password);
if (userValid)
{
FormsAuthentication.SetAuthCookie(username, false);
if (Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/")
&& !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\"))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
public ActionResult LogOff()
{
FormsAuthentication.SignOut();
return RedirectToAction("Index", "Home");
}
Now in the above code when the user tries to login we will check if the user with the given user credentials
exist in the user database or not. If it exist we set the authentication ticket and move forward.
Now if our password was encrypted or hashed we would have done the
same operation on the user entered password before checking in database,
but since the password is in plain text lets just authenticate directly.
Now before moving ahead let us look at the view that will take the user credentials and perform
the authentication.
Let us now create a simple Controller
which will contain two actions. One action that can be performed by
any authenticated user and other that can be performed by the users in "admin" role only.
public class HomeController : Controller
{
[Authorize]
public ActionResult Index()
{
ViewBag.Message = "This can be viewed only by authenticated users only";
return View();
}
[Authorize(Roles="admin")]
public ActionResult AdminIndex()
{
ViewBag.Message = "This can be viewed only by users in Admin role only";
return View();
}
}
Now when we run the application, we can see that the users will be asked to enter their credentials when they
try to access the Home controller's views. Upon successful login they will be able to see the Index page.
But if we try to look at the AdminIndex
page we will not be able to see that. The reason for that is that
currently the users roles are not being used.
Facilitating Roles extraction using the authentication ticket
Now to use the roles specified in our database, there is one thing to understand.
When Forms authentication is being used, whenever the need for authentication arises,
the ASP.NET framework checks with the current IPrinciple
type object.
The user ID and Role contained in this IPrinciple
type object will determine whether the
user is allowed access or not.
So far we have not written code to push our user's Role details in this principle object.
To do that we need to override a method called FormsAuthentication_OnAuthenticate
in global.asax
.
This method is called each time ASP.NET framework tries to check authentication and
authorization with respect to the current Principle.
What we need to do now is to override this method. Check for the authentication ticket
(since the user has already been validated and the ticket was created)
and then supply this User/Role information in the IPrinciple
type object.
We can implement our custom Principle type too but to keep it simple,
we will simply create a GenericPriciple
object and set our user specific details into it
protected void FormsAuthentication_OnAuthenticate(Object sender, FormsAuthenticationEventArgs e)
{
if (FormsAuthentication.CookiesSupported == true)
{
if (Request.Cookies[FormsAuthentication.FormsCookieName] != null)
{
try
{
string username = FormsAuthentication.Decrypt(Request.Cookies[FormsAuthentication.FormsCookieName].Value).Name;
string roles = string.Empty;
using (userDbEntities entities = new userDbEntities())
{
User user = entities.Users.SingleOrDefault(u => u.username == username);
roles = user.Roles;
}
e.User = new System.Security.Principal.GenericPrincipal(
new System.Security.Principal.GenericIdentity(username, "Forms"), roles.Split(';'));
}
catch (Exception)
{
}
}
}
}
Note: In MVC 4 and later versions, this event will not work. To make the custom forms authentication work in MVC 4 and later versions, we need to put this code in Application_PostAuthenticateRequest
event.
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
if (FormsAuthentication.CookiesSupported == true)
{
if (Request.Cookies[FormsAuthentication.FormsCookieName] != null)
{
try
{
string username = FormsAuthentication.Decrypt(Request.Cookies[FormsAuthentication.FormsCookieName].Value).Name;
string roles = string.Empty;
using (userDbEntities entities = new userDbEntities())
{
User user = entities.Users.SingleOrDefault(u => u.username == username);
roles = user.Roles;
}
HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(
new System.Security.Principal.GenericIdentity(username, "Forms"), roles.Split(';'));
}
catch (Exception)
{
}
}
}
}
And now when we run our application we can even access the AdminIndex
page. Now we have all the steps required
to implement the custom forms authentication in place. We have successfully implemented custom forms authentication
in an ASP.NET MVC application.
Note: This article contains code snippets that are only for
demonstration of concepts of the article and it does not follow any best practices Only the logic should be taken
from this article as the code in the article is not of production quality.
Point of interest
In this article we have tried to look into ASP.NET roles and membership API. We saw how MVC project comes with
the default Membership API implementation and some default Controllers and view which can be utilized to easily
integrate forms authentication in an MVC application. We also saw how we can take full control over the forms
authentication and implement custom forms authentication. This article has been written from a beginner's perspective.
hope this has been informative.
History
-
16 April 2013: First version.