Custom Authorization Filter in MVC

Custom Authorization Filter in ASP.NET 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. There are certain scenarios in your projects on which you may need to customize the Authorization Attribute instead of using the built-in Authorization Attribute. So let us discuss with an example of when and how to customizing the authorization filter in ASP.NET MVC Application 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 a new ASP.NET MVC application:

Open Visual Studio in Administrator mode and create a new project. To do so, select File => New => Project option as shown in the below image.

Customizing Authorization Filter in ASP.NET MVC

After clicking on the “Project” link a new dialog will pop up. From that window, we are going to select web templates from the left pane and from the middle pane, select the “ASP.NET Web Application” template. Provide a meaningful name such as “CustomAuthenticationinMVC” to your project and click on the OK button as shown below.

Customizing Authorization Filter in ASP.NET MVC

Once you click on the OK button a new dialog will pop up for selecting the project template. In this dialog, we are going to choose the MVC project template and then we are going to choose Authentication type. For selecting the Authentication type, just click on the Change Authentication button, a new dialog will pop up with the name “Change Authentication” here we are going to choose “Individual User Accounts” and then click on the OK button as shown below.

Customizing Authorization Filter in ASP.NET MVC

Once you click on the OK button, it will take some time to create the project for us.

Add a folder called DAL to the project.

To create a folder, right-click on the Project => Add => New Folder option which will create a new folder and then rename the folder name as DAL

Adding ADO.NET Data Model

Right-click on the DAL folder then select Add => New Item from the context menu. Then select Data from the left pane and from the middle pane select ADO.NET data Model, Provide a meaningful name and click on the Add button as shown below.

Customizing Authorization Filter in ASP.NET MVC

From the next window, select Generate From Database and click on the Next button as shown below. As our database is already created, so we are going to use the database first approach of entity framework. This is the reason why we choose the Generate From Database option.

Customizing Authorization Filter in ASP.NET MVC

In the next screen, click on the New Connection and provide the necessary details, select the database where you created the USERS table and click on the OK button 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 the Next button as shown  in the below image.

Customizing Authorization Filter in ASP.NET MVC

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

Customizing Authorization Filter in ASP.NET MVC

That’s it. our model is ready.

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 then copy and paste the following code

public enum Role
{
    SUPERADMIN = 1,
    ADMIN = 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;
    }
}
Modify the Login POST method

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");
}
Modifying Global.asax.cs file

Open Global.asax.cs file and then copy and paste the following code in it.

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)));
    }
}
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 the “About” method will give access to only Super Admin and for the Contact method, we give access to only Admin.

Decorate HomeController with [Authorize] attribute first to restrict unauthorized access, decorate the 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 the 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 then 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)));
    }

    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. 

3 thoughts on “Custom Authorization Filter in MVC”

  1. Hi
    Can you please explain, In controller you have added [AuthorizeRole(Role.SUPERADMIN)], which does not work. I think that would be [AuthorizeRole(Role.Administrator)] , because, we have the Role.css class and there is enum having values for Administrator, UserWithPriviliges, and User. Please provide the correct code for this.
    thanks

  2. i change two things to make this example workable:

    1) Modify the code part in Login POST method as shown below
    USER user = repository.GetUserDetails(model.Email, model.Password);

    2) change the user role Role.cs class to:
    public enum Role
    {
    SUPERADMIN = 1,
    ADMIN = 2,
    USER = 3,
    }

    This way, it worked for me now. Thanks

Leave a Reply

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