This article is a continuation of my previous article about “Building Web Application Using Entity Framework And MVC 5: Part 1”.
This article explains the following:
- Creating a Login page that would validate and authenticate the user using Forms Authentication
- Creating a custom role-based page authorization using a custom Authorize filter
For this specific demo I will show how to create a simple Login form by implementing a custom authentication and role-based page authorization without using ASP.NET Membership or ASP.NET Identity. If you want to build an app that allows users to login using their social media accounts like Facebook, Twitter, Google Plus and so on then you may want explore ASP.NET Identity instead.
Note that I will not elaborate more on the details about the model, view and controllers function so before proceeding further, I'd suggest you to check my previous article “Building Web Application Using Entity Framework And MVC 5: Part 1” first, especially if you are new to ASP.NET MVC web development.
Before we get our hands dirty let's talk about a bit of security in general.
Forms Authentication Overview
Security is an integral part of any Web-based application. The majority of web sites currently heavily rely on authentication and authorization for securing their application. You can think of a web site as somewhat analogous to a company office where an office is open for people like applicants or messengers to come, but there are certain parts of the facility, such as workstations and conference rooms, that are accessible only to people, such as employees, with certain credentials. An example is when a you build a shopping cart application that accepts user's credit card information for payment purposes and stores them into your database. ASP.NET helps protect your database from public access by providing authentication and authorization.
Forms authentication lets you authenticate users using your own code and then maintain an authentication token in a cookie or in the page URL. To use forms authentication, you create a login page that collects credentials from the user that includes code to authenticate the credentials. Typically you configure the application to redirect requests to the login page when users try to access a protected resource, such as a page that requires authentication. If the user's credentials are valid, you can call methods of the FormsAuthentication class to redirect the request back to the originally requested resource with an appropriate authentication ticket (cookie).
Let’s get our hands dirty!
As a recap, here's the previous project structure below:
Implementing the Login Page
STEP 1: Enabling Forms Authentication
To allow forms authentication, the very first thing to do in your application is to configure FormsAuthentication that manages forms authentication services to your web application. The default authentication mode for ASP.NET is “Windows”. To enable forms authentication, add the <authentication> and <forms> elements under the <system.web> element in your web.config as in the following:
- <system.web>
- <authentication mode="Forms">
- <forms loginUrl="~/Account/Login" defaultUrl="~/Home/Welcome"></forms>
- </authentication>
- </system.web>
Setting the loginUrl enables the application to determine where to redirect an unauthenticated user who attempts to access a secured page. The defaultUrl redirects users to the specified page after successful log-in.
STEP 2: Adding the UserLoginView Model
Let's go ahead and create a model view class for our Login page by adding the following code within the “UserModel” class:
- public class UserLoginView
- {
- [Key]
- public int SYSUserID { get; set; }
- [Required(ErrorMessage = "*")]
- [Display(Name = "Login ID")]
- public string LoginName { get; set; }
- [Required(ErrorMessage = "*")]
- [DataType(DataType.Password)]
- [Display(Name = "Password")]
- public string Password { get; set; }
- }
The fields defined above will be used in our Login page. You may also see that the fields are decorated with Required, Display and DataType attributes. These attributes are called Data Annotations.
STEP 3: Adding the GetUserPassword() Method
Add the following code for the “UserManager.cs” class:
- public string GetUserPassword(string loginName) {
- using (DemoDBEntities db = new DemoDBEntities()) {
- var user = db.SYSUsers.Where(o => o.LoginName.ToLower().Equals(loginName));
- if (user.Any())
- return user.FirstOrDefault().PasswordEncryptedText;
- else
- return string.Empty;
- }
- }
As the method name suggests, it gets the corresponding password from the database for a specific login name using a LINQ query.
STEP 4: Adding the Login Action Method
Add the following code for the “AccountController” class:
- public ActionResult LogIn() {
- return View();
- }
-
- [HttpPost]
- public ActionResult LogIn(UserLoginView ULV, string returnUrl) {
- if (ModelState.IsValid) {
- UserManager UM = new UserManager();
- string password = UM.GetUserPassword(ULV.LoginName);
-
- if (string.IsNullOrEmpty(password))
- ModelState.AddModelError("", "The user login or password provided is incorrect.");
- else {
- if (ULV.Password.Equals(password)) {
- FormsAuthentication.SetAuthCookie(ULV.LoginName, false);
- return RedirectToAction("Welcome", "Home");
- }
- else {
- ModelState.AddModelError("", "The password provided is incorrect.");
- }
- }
- }
-
-
- return View(ULV);
- }
As you can see, there are two methods above with the same name. The first one is the "Login" method that simply returns the LogIn.cshtml view. We will create this view in the next step. The second one is also named "Login" but it is decorated with the "[HttpPost]" attribute. This attribute specifies the overload of the "Login" method that can be invoked only for POST requests.
The second method will be triggered once the Button "LogIn" is fired. What it does is, first it will check if the required fields are supplied so it checks for ModelState.IsValid condition. It will then create an instance of the UserManager class and call the GetUserPassword() method by passing the user LoginName value supplied by the user. If the password returns an empty string then it will display an error to the view. If the password supplied is equal to the password retrieved from the database then it will redirect the user to the Welcome page, otherwise it will display an error stating that the login name or password supplied is invalid.
STEP 5: Adding the Login View
Before adding the view, be sure to build your application first to ensure that the application is error-free. After a successful build, navigate to the “AccountController” class and right-click on the Login action method and then select “Add View”. This will bring up the following dialog below:
Take note of the values supplied for each field. Now click on Add to let Visual Studio scaffold the UI for you. The is the modified HTML markup:
- @model MVC5RealWorld.Models.ViewModel.UserLoginView
-
- @{
- ViewBag.Title = "LogIn";
- Layout = "~/Views/Shared/_Layout.cshtml";
- }
-
- <h2>LogIn</h2>
-
- @using (Html.BeginForm())
- {
- @Html.AntiForgeryToken()
-
- <div class="form-horizontal">
- <hr />
- @Html.ValidationSummary(true, "", new { @class = "text-danger" })
- <div class="form-group">
- @Html.LabelFor(model => model.LoginName, htmlAttributes: new { @class = "control-label col-md-2" })
- <div class="col-md-10">
- @Html.EditorFor(model => model.LoginName, new { htmlAttributes = new { @class = "form-control" } })
- @Html.ValidationMessageFor(model => model.LoginName, "", new { @class = "text-danger" })
- </div>
- </div>
-
- <div class="form-group">
- @Html.LabelFor(model => model.Password, htmlAttributes: new { @class = "control-label col-md-2" })
- <div class="col-md-10">
- @Html.EditorFor(model => model.Password, new { htmlAttributes = new { @class = "form-control" } })
- @Html.ValidationMessageFor(model => model.Password, "", new { @class = "text-danger" })
- </div>
- </div>
-
- <div class="form-group">
- <div class="col-md-offset-2 col-md-10">
- <input type="submit" value="Login" class="btn btn-default" />
- </div>
- </div>
- </div>
- }
-
- <div>
- @Html.ActionLink("Back to Main", "Index", "Home")
- </div>
STEP 6: Implementing the Logout Functionality
The logout code is quiet simple. Just add the following method to the AccountController class.
- [Authorize]
- public ActionResult SignOut() {
- FormsAuthentication.SignOut();
- return RedirectToAction("Index", "Home");
- }
The FormsAuthentication.SignOut method removes the forms-authentication ticket from the browser. We then redirect the user to the Index page after signing out.
Here’s the corresponding action link for the Logout:
- @Html.ActionLink("Signout","SignOut","Account")
STEP 7: Run the Application
Running your application should display something like the following.
When validation triggers:
After successful Logging in:
After logging out:
That was simple! Now let’s have a look at how to implement a simple role-based page authorization.
Implementing a Simple Role-Based Page Authorization
Authorization is a function that specifies access rights to a certain resource or page. One practical example is having a page that only a certain user role can have access to. For example, only allow an administrator to access the maintenance page for your application. In this section we will create a simple implementation on how to do that.
STEP 1: Creating the UserIsInRole Method
Add the following code to the "UserManager" class:
- public bool IsUserInRole(string loginName, string roleName) {
- using (DemoDBEntities db = new DemoDBEntities()) {
- SYSUser SU = db.SYSUsers.Where(o => o.LoginName.ToLower().Equals(loginName))?.FirstOrDefault();
- if (SU != null) {
- var roles = from q in db.SYSUserRoles
- join r in db.LOOKUPRoles on q.LOOKUPRoleID equals r.LOOKUPRoleID
- where r.RoleName.Equals(roleName) && q.SYSUserID.Equals(SU.SYSUserID)
- select r.RoleName;
-
- if (roles != null) {
- return roles.Any();
- }
- }
-
- return false;
- }
- }
The method above takes the loginName and roleName as parameters. What it does is it checks for the existing records in the user's table and then validates if the corresponding user has roles assigned to it.
STEP 2: Creating a Custom Authorization Attribute Filter
If you remember, we are using the [Authorize] attribute to restrict anonymous users from accessing a certain action method. The [Authorize] attribute provides filters for users and roles and it’s fairly easy to implement it if you are using a membership provider. Since we are using our own database for storing users and roles, we need to implement our own authorization filter by extending the AuthorizeAttribute class.
The AuthorizeAttribute specifies that access to a controller or action method is restricted to users who meet the authorization requirements. Our goal here is to allow page authorization based on user roles and nothing else. If you want to implement custom filters to do a certain task and value the separation of concerns then you may want to look at IAutenticationFilter instead.
To start, add a new folder and name it “Security”. Then add the “AuthorizeRoleAttribute” class. The following is a screen shot of the structure:
Here’s the code block for our custom filter:
- using System.Web;
- using System.Web.Mvc;
- using MVC5RealWorld.Models.DB;
- using MVC5RealWorld.Models.EntityManager;
-
- namespace MVC5RealWorld.Security
- {
- public class AuthorizeRolesAttribute : AuthorizeAttribute
- {
- private readonly string[] userAssignedRoles;
- public AuthorizeRolesAttribute(params string[] roles) {
- this.userAssignedRoles = roles;
- }
- protected override bool AuthorizeCore(HttpContextBase httpContext) {
- bool authorize = false;
- using (DemoDBEntities db = new DemoDBEntities()) {
- UserManager UM = new UserManager();
- foreach (var roles in userAssignedRoles) {
- authorize = UM.IsUserInRole(httpContext.User.Identity.Name, roles);
- if (authorize)
- return authorize;
- }
- }
- return authorize;
- }
- protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) {
- filterContext.Result = new RedirectResult("~/Home/UnAuthorized");
- }
- }
- }
There are two main methods in the class above that we have overridden. The AuthorizeCore() method is the entry point for the authentication check. This is where we check the roles assigned for certain users and returns the result specifying whether or not the user is allowed to access a page. The HandleUnuathorizedRequest() is a method in which we redirect un-authorized users to a certain page.
STEP 3: Adding the AdminOnly and UnAuthorized page
Now switch back to “HomeController” and add the following code:
- [AuthorizeRoles("Admin")]
- public ActionResult AdminOnly() {
- return View();
- }
-
- public ActionResult UnAuthorized() {
- return View();
- }
If you notice, we decorated the AdminOnly action with our custom authorization filter by passing the value of “Admin” as the role name. This means that only allow admin users have access to the AdminOnly page. To support multiple role access, just add another role name by separating it with a comma, for example [AuthorizeRoles(“Admin”,”Manager”)]. Note that the value of “Admin” and “Manager” should match the role names from your database. And finally, be sure to reference the namespace below before using the AuthorizeRoles attribute:
- using MVC5RealWorld.Security;
Here’s the AdminOnly.cshtml view:
- @{
- ViewBag.Title = "AdminOnly";
- Layout = "~/Views/Shared/_Layout.cshtml";
- }
-
- <h2>For Admin users only!</h2>
- And here’s the UnAuthorized.cshtml view:
- @{
- ViewBag.Title = "UnAuthorized";
- Layout = "~/Views/Shared/_Layout.cshtml";
- }
And here’s the UnAuthorized.cshtml view:
- @{
- ViewBag.Title = "UnAuthorized";
- Layout = "~/Views/Shared/_Layout.cshtml";
- }
-
- <h2>Unauthorized Access!</h2>
- <p>Oops! You don't have permission to access this page.</p>
-
- <div>
- @Html.ActionLink("Back to Main", "Welcome", "Home")
- </div>
STEP 4: Testing the functionality
Before we test the functionality, lets add an admin user to the database first. For this demo I have inserted the following data to the database:
- INSERT INTO SYSUser (LoginName,PasswordEncryptedText, RowCreatedSYSUserID, RowModifiedSYSUserID)
- VALUES ('Admin','Admin',1,1)
- GO
- INSERT INTO SYSUserProfile (SYSUserID,FirstName,LastName,Gender,RowCreatedSYSUserID, RowModifiedSYSUserID)
- VALUES (2,'Vinz','Durano','M',1,1)
- GO
-
- INSERT INTO SYSUserRole (SYSUserID,LOOKUPRoleID,IsActive,RowCreatedSYSUserID, RowModifiedSYSUserID)
- VALUES (2,1,1,1,1)
Okay, now we have data to test and we are ready to run the application.
STEP 5: Run the Application
The following are some of the screenshots captured during my test.
When logging in as a normal user and accessing the following URL: http://localhost:15599/Home/AdminOnly
When logging in as an Admin user and accessing the following URL: http://localhost:15599/Home/AdminOnly
In my next article I will show how to do Edit, Update and Delete operations within our MVC application, so stay tuned.
That’s it! I hope you find this article useful.