Click here to Skip to main content
15,868,016 members
Articles / Web Development / HTML

ASP.NET MVC 5: Building Your First Web Application - Part 2

Rate me:
Please Sign up or sign in to vote.
4.96/5 (21 votes)
6 Aug 2016CPOL8 min read 62.8K   1.4K   30   17
This article explains how to create a Login page and create a custom role-based page authorization in ASP.NET MVC 5 application.

Introduction

This article is a continuation of my previous article about “ASP.NET MVC 5: Building Your First Web Application - Part 1”.

What you will learn:

  • Creating a Login page that would validate and authenticate users 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 “ASP.NET MVC 5: Building Your First Web Application - 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. 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:

Image 1

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:

XML
<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 successful log-in.

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:

C#
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. Again, these attributes are called Data Annotations.

Adding the GetUserPassword() Method

Add the following code for the “UserManager” class:

C#
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.

Adding the Login Action Method

Add the following code for the “AccountController” class:

C#
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 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.

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:

Image 2

Figure 2: Add View dialog

Take note of the values supplied for each field. Now click on Add to let Visual Studio scaffolds the UI for you. Here is the modified HTML markup:

Razor
@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> 

Implementing the Logout Functionality

The logout code is quiet simple. Just add the following method below in the AccountController class.

C#
[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:

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

Output

Running your application should display something like the following.

When validation triggers:

Image 3

Figure 3: Validation triggers

After successful Logging in:

Image 4

Figure 4: Successful logging-in

After logging out:

Image 5

Figure 5: After logging out

Pretty 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.

Creating the UserIsInRole Method

Add the following code to the "UserManager" class:

C#
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.

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:

Image 6

Figure 6: Project Structure

Here’s the code block for our custom filter:

C#
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.

Adding the AdminOnly and UnAuthorized Pages

Now switch back to “HomeController” and add the following code:

C#
[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. Finally, be sure to reference the namespace below before using the AuthorizeRoles attribute:

C#
using MVC5RealWorld.Security; 

Here’s the AdminOnly.cshtml View:

Razor
@{  
    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:

C#
@{  
    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 I have inserted the following data to the database:

SQL
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

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

Image 7

Figure 7: Un-authorized access

When logging in as an Admin user and accessing the following URL: http://localhost:15599/Home/AdminOnly

Image 8

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. You can check the next part here: ASP.NET MVC 5: Building Your First Web Application - Part 3

That’s it! I hope you find this article useful.

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.

License

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


Written By
Architect
United States United States
A code monkey who loves to drink beer, play guitar and listen to music.

My Tech Blog: https://vmsdurano.com/
My Youtube Channel: https://www.youtube.com/channel/UCuabaYm8QH4b1MAclaRp-3Q

I currently work as a Solutions Architect and we build "cool things" to help people improve their health.

With over 14 years of professional experience working as a Sr. Software Engineer specializing mainly on Web and Mobile apps using Microsoft technologies. My exploration into programming began at the age of 15;Turbo PASCAL, C, C++, JAVA, VB6, Action Scripts and a variety of other equally obscure acronyms, mainly as a hobby. After several detours, I am here today on the VB.NET to C# channel. I have worked on Web Apps + Client-side technologies + Mobile Apps + Micro-services + REST APIs + Event Communication + Databases + Cloud + Containers , which go together like coffee crumble ice cream.

I have been awarded Microsoft MVP each year since 2009, awarded C# Corner MVP for 2015, 2016,2017 and 2018, CodeProject MVP, MVA, MVE, Microsoft Influencer, Dzone MVB, Microsoft ASP.NET Site Hall of Famer with All-Star level and a regular contributor at various technical community websites such as CSharpCorner, CodeProject, ASP.NET and TechNet.

Books written:
" Book: Understanding Game Application Development with Xamarin.Forms and ASP.NET
" Book (Technical Reviewer): ASP.NET Core and Angular 2
" EBook: Dockerizing ASP.NET Core and Blazor Applications on Mac
" EBook: ASP.NET MVC 5- A Beginner's Guide
" EBook: ASP.NET GridView Control Pocket Guide

Comments and Discussions

 
QuestionStuck on authorizeRolesAttribute.cs Pin
MD Rehman25-Oct-18 2:52
MD Rehman25-Oct-18 2:52 
QuestionSignin not working Pin
Ahmad Bilal5-Jul-18 6:21
Ahmad Bilal5-Jul-18 6:21 
AnswerRe: Signin not working Pin
Vincent Maverick Durano4-Sep-18 10:19
professionalVincent Maverick Durano4-Sep-18 10:19 
Bugconflict with another coloumn of the same name Pin
Member 1364325425-Jan-18 0:26
Member 1364325425-Jan-18 0:26 
QuestionError Pin
Member 134976591-Nov-17 3:44
Member 134976591-Nov-17 3:44 
AnswerRe: Error Pin
Vincent Maverick Durano4-Dec-17 18:56
professionalVincent Maverick Durano4-Dec-17 18:56 
QuestionViews not picking up the Layout = "~/Views/Shared/_Layout.cshtml" Pin
Wayne Hollier20-Sep-17 9:13
Wayne Hollier20-Sep-17 9:13 
AnswerRe: Views not picking up the Layout = "~/Views/Shared/_Layout.cshtml" Pin
Vincent Maverick Durano21-Sep-17 15:00
professionalVincent Maverick Durano21-Sep-17 15:00 
GeneralRe: Views not picking up the Layout = "~/Views/Shared/_Layout.cshtml" Pin
Wayne Hollier21-Sep-17 20:01
Wayne Hollier21-Sep-17 20:01 
QuestionAdmin Security Issues Pin
Member 132950756-Jul-17 7:05
Member 132950756-Jul-17 7:05 
AnswerRe: Admin Security Issues Pin
Vincent Maverick Durano9-Jul-17 17:00
professionalVincent Maverick Durano9-Jul-17 17:00 
PraiseAnother well written article..... Pin
Member 1325335611-Jun-17 10:51
Member 1325335611-Jun-17 10:51 
GeneralRe: Another well written article..... Pin
Vincent Maverick Durano21-Sep-17 15:01
professionalVincent Maverick Durano21-Sep-17 15:01 
Questionpassword encryption Pin
Mekki Ahmedi12-Aug-16 0:58
Mekki Ahmedi12-Aug-16 0:58 
AnswerRe: password encryption Pin
Vincent Maverick Durano12-Aug-16 2:12
professionalVincent Maverick Durano12-Aug-16 2:12 
GeneralMy vote of 5 Pin
D V L1-Jul-16 7:33
professionalD V L1-Jul-16 7:33 
GeneralRe: My vote of 5 Pin
Vincent Maverick Durano1-Jul-16 7:42
professionalVincent Maverick Durano1-Jul-16 7:42 

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.