Share via


Entity Framework and ASP.NET MVC 5: Building Web Application (Part 2)

Introduction

This article is a continuation of the previous article about “Entity Framework and ASP.NET MVC 5: Building Web Application (Part 1)”. 

For this specific demo, we will take a look at 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 Microsoft, Facebook, Twitter, Google Plus and so on, then you may want to explore ASP.NET Identity instead.

What you will learn:

  • Build a Login page that would validate and authenticate users using Forms Authentication
  • Build a custom role-based page authorization using a custom Authorize filter

Note that this part of the series will not elaborate on the details about the Model, View and Controller functions in ASP.NET MVC. Before proceeding further, please head to the previous article first, especially if you are new to ASP.NET MVC web development. As a quick reference, here's the current list of the series for this application:

  • Entity Framework and ASP.NET MVC 5: Building Web Application (Part 1)
  • Entity Framework and ASP.NET MVC 5: Building Web Application (Part 2)  ~ You are here
  • Entity Framework and ASP.NET MVC 5: Building Web Application (Part 3)
  • Entity Framework and ASP.NET MVC 5: Building Web Application (Part 4)
  • Entity Framework and ASP.NET MVC 5: Building Web Application (Part 5)
  • Entity Framework and ASP.NET MVC 5: Building Web Application (Part 6)

Before we get our hands dirty, let's talk about a bit of web 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 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. For more information, read on: ASP.NET Web Application Security Fundamentals

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). You can read more about Forms Authentication here.

Let’s get our hands dirty!

As a recap, here's the previous project structure below:

Figure 1: Project Solution Structure

Implementing the Login Page

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 un-authenticated user who attempts to access a secured page. The defaultUrl redirects users to the specified page after a successful log-in.

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 notice that the fields are decorated with Required, Display and DataType attributes. Again, these attributes are called Data Annotations.

The GetUserPassword() Method

Add the following code for the “UserManager” 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.

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.");  
                    }  
                }  
            }  
   
            // If we got this far, something failed, redisplay form  
            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 HTML Button "LogIn" is fired. What it does is, first it will check if the required fields are supplied, then 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.

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:


Figure 2: Add View dialog

Take note of the values supplied for each field. Now click Add to let Visual Studio scaffolds the UI for you. Here 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>

The Logout Functionality

The logout code is quiet simple. Just add the following method below in 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" that you can add within your "Home" page:

@Html.ActionLink("Signout","SignOut","Account")

Output

Running your application should display something like the following.

When validation triggers:


Figure 3: Validation triggers

After successful Logging in:


Figure 4: Successful logging-in

After logging out:

Figure 5: After logging out

Pretty simple! Now let’s have a look at how to implement a simple role-based page authorization.

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.

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.

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 screenshot of the structure:


Figure 6: Project 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 ~ the "UnAuthorized" page.

The AdminOnly and UnAuthorized Pages

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 we only allow admin users to 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 with the role names from your database otherwise you'll end up getting unauthorize response. 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>

Testing the Functionality

Before we test the functionality, lets add an admin user to the database first. For this demo we 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.

Output

Here's an output when logging in as a normal user and accessing the URL http://localhost:15599/Home/AdminOnly:


Figure 7: Unauthorized access

When logging in as an Admin user and accessing the the same URL.


*
Figure 8: The Admin page*

In the next part of this series, we will see how to do Fetch, Edit, Update and Delete (FEUD) operations within our MVC 5 application. 

Summary

In this series, we've learned how to implement a simple login page and how to integrate a custom role-based page authorization in ASP.NET MVC 5 application.

See Also