Custom Authorization Filter in MVC

Custom Authorization Filter in MVC

In this article, I am going to discuss Custom Authorization Filter in MVC with an example. Please read our previous article before proceeding to this article where we discussed the basics of Authorization Filter in MVC application. But there are certain scenarios in your projects on which you may need to use customize the Authorization Attribute instead of using the built-in Authorization Attribute.

So let us discuss with an example of how to customizing the authorization filter in MVC and mapping it to the default Authorize filter.

SQL Script for this demo:

We are going to use the following USERS table in this demo

Customizing Authorization Filter in ASP.NET MVC

Please use below SQL Script to create and populate the USERS table with the required data.

CREATE TABLE USERS
(
  UserID BIGINT PRIMARY KEY IDENTITY,
  UserName VARCHAR(100) NOT NULL,
  Roles VARCHAR(100) NOT NULL,
  [Password] VARCHAR(100) NOT NULL,
)

-- UserID is the auto-generated unique id
-- UserName is the unique value i.e. Email ID of the user
-- Roles can be ADMIN, SUPERADMIN OR USER
-- WE CAN PROVIDE 1 OR MORE USER SEPARATED BY ','

--INSERT THE FOLLOWING TEST DATA FOR TESTING PURPOSE
INSERT INTO USERS VALUES('superadmin@gmail.com','SUPERADMIN,ADMIN,USER', 'superadmin')
INSERT INTO USERS VALUES('admin@gmail.com','ADMIN', 'admin')
INSERT INTO USERS VALUES('adminuser@gmail.com','ADMIN,USER', 'adminuser')
INSERT INTO USERS VALUES('user@gmail.com','USER', 'user')

Create an MVC application:

Open Visual Studio in Administrator mode and create a new project

Select File => New => Project

Customizing Authorization Filter in ASP.NET MVC

After clicking on “Project” link a new dialog will pop up.

In that we are going to select web templates from the left pane after selecting web template, we need to select the web template as “ASP.NET Web Application” as shown below.

Customizing Authorization Filter in ASP.NET MVC

After selecting this project template next we are going to name the project as “CustomAuthenticationinMVC” and clicking on the OK button a new dialog will pop up with Name “New ASP.NET Project” for selecting project Templates.

Customizing Authorization Filter in ASP.NET MVC

In this dialog, we are going to choose MVC project template and then we are going to choose Authentication type for doing that just click on Change Authentication button, a new dialog will pop up with the name “Change Authentication” here we are going to choose “Individual User Accounts” click on OK Button.

It will take some to time create the project for us. Once the project is created let’s see the folder structure as shown below

Customizing Authorization Filter in ASP.NET MVC

Add a folder called DAL to the project.

Right Click on the Project => Add => New Folder

Rename the folder name as DAL

Add ADO.NET Data Model inside DAL Folder

Right click on DAL folder then Add => New Item

Select ADO.NET data Model, Provide a meaningful name and click on ADD button as shown below

Customizing Authorization Filter in ASP.NET MVC

From Choose Entity data Model Screen choose Generate From Database and Click Next as shown below

Customizing Authorization Filter in ASP.NET MVC

In the next screen Click on New Connection and provide the necessary details, Select the database where we created the USERS table and click on OK as shown below

Customizing Authorization Filter in ASP.NET MVC

Provide a meaningful name for the Connection String that is going to create in Web.config file and click on Next as shown below

Customizing Authorization Filter in ASP.NET MVC

in the From Choose your database objects screen, choose the USERS object, provide the namespace and click on Finish button as shown below

Customizing Authorization Filter in ASP.NET MVC

Then it will create the USER model as shown below

Customizing Authorization Filter in ASP.NET MVC

Following is the Folder structure for our EDMX file

Customizing Authorization Filter in ASP.NET MVC

Following is auto generated USER model generated by Entity Framework
namespace CustomAuthorizeInMVC.DAL
{ 
    public partial class USER
    {
        public long UserID { get; set; }
        public string UserName { get; set; }
        public string Roles { get; set; }
        public string Password { get; set; }
    }
}
Create ENUM to store user Roles

For our needs we will create the following Enum to declare roles:

Right click on the Models folder and add a class File with the name Role.cs and copy and paste the following code

public enum Role
{
    Administrator = 1,
    UserWithPrivileges = 2,
    User = 3,
}

Create a Repository to perform DB Operations

Right click on the Models folder and add a class File with the name Repository.cs and copy and paste the following code

public class Repository
{
    public USER GetUserDetails(string UserName, string Password)
    {
        USER user = new USER();
        using (UserDBContext db = new UserDBContext())
        {
            user = db.USERS.Where(u => u.UserName.ToLower() == UserName.ToLower() &&
                                  u.Password == Password).FirstOrDefault();
        }
        return user;
    }
}

Open AccountController that is present in Controllers Folder and Import the Following Namespaces

using CustomAuthorizeInMVC.Models;

using CustomAuthorizeInMVC.DAL;

using System.Web.Security;

Goto method called Login(LoginViewModel model, string returnUrl) whose type is POST

Modify the Login POST method as shown below
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginViewModel model, string returnUrl)
{

    if (!ModelState.IsValid)
    {
        return View(model);
    }
    Repository repo = new Repository();

    USER user = repo.GetUserDetails(model.UserName, model.Password);

    if (user != null)
    {
        FormsAuthentication.SetAuthCookie(user.UserName, model.RememberMe);
        FormsAuthentication.SetAuthCookie(Convert.ToString(user.UserID), model.RememberMe);

        var authTicket = new FormsAuthenticationTicket(1, user.UserName, DateTime.Now, DateTime.Now.AddMinutes(20), false, user.Roles);
        string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
        var authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
        HttpContext.Response.Cookies.Add(authCookie);

        //Based on the Role we can transfer the user to different page
        return RedirectToAction("Index", "Home");
    }
    else
    {
        ModelState.AddModelError("", "Invalid login attempt.");
        return View(model);
    }
}
Modify the LogOff method of Account Controller as shown below
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult LogOff()
{
    FormsAuthentication.SignOut();
    return RedirectToAction("Index", "Home");
}
Open Global.asax.cs file

Paste the below method

using System.Web.Security;
public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
    }
    protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
    {
        var authCookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
        if (authCookie != null)
        {
            FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
            if (authTicket != null && !authTicket.Expired)
            {
                var roles = authTicket.UserData.Split(',');
                HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(new FormsIdentity(authTicket), roles);
            }
        }
    }
}

Add AuthorizeRole.cs class file in the Models folder and copy and paste the following code.

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class AuthorizeRoleAttribute : AuthorizeAttribute
{
    public AuthorizeRoleAttribute(params object[] roles)
    {
        if (roles.Any(r => r.GetType().BaseType != typeof(Enum)))
            throw new ArgumentException("roles");

        this.Roles = string.Join(",", roles.Select(r => Enum.GetName(r.GetType(), r)));
    }
}
Now the settings required for role-based Authentication.

Let’s see what have we created and how are we going to use those

  1. We have created 3 users ADMIN, SUPERADMIN, and USER
  2. Now in Home Controller “Index” method we will give access to both Super admin and admin, for “About” method will give access to only Super Admin and for Contact method, we give access to only Admin.

Decorate HomeController with [Authorize] attribute first to restrict unauthorized access, decorate remaining three methods with respective roles as shown below.

[Authorize]
public class HomeController : Controller
{
    [AuthorizeRole(Role.SUPERADMIN, Role.ADMIN)]
    public ActionResult Index()
    {
        return View();
    }

    [AuthorizeRole(Role.SUPERADMIN)]
    public ActionResult About()
    {
        ViewBag.Message = "Your application description page.";
        return View();
    }

    [AuthorizeRole(Role.ADMIN)]
    public ActionResult Contact()
    {
        ViewBag.Message = "Your contact page.";

        return View();
    }
    [AuthorizeRole(Role.USER)]
    public ActionResult UserPage()
    {
        ViewBag.Message = "User Page";
        return View();
    }
}

Note: we need to Create a UserPage action method as this method is not created manually. Along with creating the UserPage View and copy-paste the following code in UserPage.cshtml file

@{
    ViewBag.Title = "User Page";
}
<h2>@ViewBag.Title.</h2>
<h3>@ViewBag.Message</h3>

Now we need to do a small change in our _Layout.cshtml file which is inside Shared Folder.

Add the following line below <li>@Html.ActionLink(“Contact”, “Contact”, “Home”)</li>

<li>@Html.ActionLink(“User Page”, “UserPage”, “Home”)</li>

That’s it we are ready with our application with custom authentication and authorization.

Now let’s run the application, as we have decorated HomeController with [Authorize] attribute, we will get Login page first instead of Default HomeController Index method.

If we see the URL it is not directly called Account/Login method, there is extra ReturnUrl

http://localhost:58040/Account/Login?ReturnUrl=%2F

Let’s see the route config
public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
        );
    }
}

So when it goes to HomeController directly it doesn’t get authenticated so it redirects to the Login page in AccountController. Now enter the required credentials created by us in the database. I am entering Super admin details and submit the page as shown below

Customizing Authorization filter in ASP.NET MVC

Then it will navigate us to the index page as index page is accessible to Super Admin. Now click on every page and all pages are accessible to Super admin as we set in the database as shown below.

Customizing Authorization filter in ASP.NET MVC

Now Login with admin@gmail.com and try to navigate to UserPage link and it will navigate to the Login page as UserPage does not have access to Admin.

There is a problem:

When the user is authenticated and if the user does not have access to a particular page then instead of Navigating to the Login page we need to navigate to Access denied page.

Let’s modify the Requirement.

If the user is not authenticated navigate to the Login Page. If the user is authenticated but Access is not given for a particular page then navigate to the Access Denied page.

Add the AccessDenied action method with AllowAnonymous attribute as shown below

[AllowAnonymous]
public ActionResult AccessDenied()
{
      return View();
}

Add AccessDenied View and Copy and Paste the following code as shown below

@{
    ViewBag.Title = "Unauthorized Access";
}

<h1 class="text-danger">Unauthorized Access</h1>
<h2 class="text-danger">You don’t have permission to view this page.</h2>

Then open AuthorizeRole.cs file which is in Models folder and copies and paste the following code

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class AuthorizeRoleAttribute : AuthorizeAttribute
{
    public AuthorizeRoleAttribute(params object[] roles)
    {
        if (roles.Any(r => r.GetType().BaseType != typeof(Enum)))
            throw new ArgumentException("roles");

        this.Roles = string.Join(",", roles.Select(r => Enum.GetName(r.GetType(), r)));
    }

    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        base.OnAuthorization(filterContext);
        if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
        {
            filterContext.Result = new RedirectResult("~/Account/Login");
            return;
        }

        if (filterContext.Result is HttpUnauthorizedResult)
        {
            filterContext.Result = new RedirectResult("~/Account/AccessDenied");
            return;
        }
    }
}

Now build the application and run. Check everything is working as expected.

In the next article, I am going to discuss the Custom Authentication Filter in MVC Application. Here, in this article, I try to explain the Custom Authorization Filter in MVC application step by step with a real-time example. 

Leave a Reply

Your email address will not be published. Required fields are marked *