Tuesday, December 20, 2016

Custom Role Based Authentication & Authorization in ASP.NET MVC

Introduction 

According to the requirements of various projects, Some times we need to build unique authentication and authorization process. This post is about role based customized
authentication and authorization. 


Create a MVC Web Project using Visual Studio

























Now clear all class  files in Controllers , Models and Views folders using the  
tree view of  Solution Explorer. 

Then Add AccountController class  in Controller folder and at the same time add a view to the Index method. 

Now we are going to concern about the Model section.
Add a Class called Account in the Model folder. 

 public class Account
    {
       
        [Display (Name = "Username")]
        public string  Username { get; set; }

        [Display(Name = "Password")]
        public string  Password { get; set; }

        public string[] Roles { get; set; }
    }

In here we defined a string array called Roles to store the various roles that an account can have. 

After that add another class called AccountModel in the Model folder. 

  public class AccountModel    {     
   private List<Account> listAccounts = new List<Account>();
        public AccountModel()        {     
listAccounts = DBTransactions.GetAccountList();
        }      
  public Account FindAccount(string username)        {      
      return listAccounts.Where(acc => acc.Username.Equals(username)).FirstOrDefault();     
   }
        public Account Login (string username , string password)        {   
         return listAccounts.Where(acc => acc.Username.Equals(username) &&                 acc.Password.Equals(password)).FirstOrDefault();    
    }
    }

Note that , DBTransactions is a Static class  implemented by me in the project to get database data from the SQL Server Managemet Studio. I have used Entity Framework to make it easier to access table and stored procedures in the database. Make sure you have added the correct connection string to the database you are using in the web.config file. 
    
In here we listed down the available accounts in the database , Find the Account which is match with the entered user name and the password by the user. 

Now we can go to the RouteConfig class in the App_Start folder and change the configuration as follows. 

 public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

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

Then add a new folder called ViewModels in the folder structure in Solution Explorer.  

After that add a class called AccountViewModel in the ViewModel folder. 

 public class AccountViewModel
    {
        public Account Account {get ; set ; }
    }

We are going to use this ViewModel for the Index View of AccountController. 

This is my View which allows user to login the system. 

@model SessionSecurity.ViewModel.AccountViewModel
<!DOCTYPE html>
<html>
<head>    
<meta name="viewport" content="width=device-width" />    
<title>Index </title>
</head>
<body>   
 <h6>@ViewBag.Error</h6>    
@using (Html.BeginForm("Login", "Account"))    {    
    <table cellpadding="2" cellspacing="2">   
         <tr>               
 <td> @Html.LabelFor(model=> model.Account.Username)</td>                <td>@Html.TextBoxFor(model => model.Account.Username)</td>          
  </tr>           
 <tr>               
 <td> @Html.LabelFor(model => model.Account.Password)</td>                <td>@Html.PasswordFor(model => model.Account.Password)</td>      
  </tr>         
   <tr>         
       <td> &nbsp; </td>          
      <td><input type="submit" name="login" value="Login"> </td>      
      </tr>    
    </table> 
   }
</body>
</html>

Then we need to develop the controller method relevant to Login and LogOut Actions. 

In AccountController class I have implemented the Login and LogOut Actions as follows.


       [HttpPost]
        public ActionResult Login (AccountViewModel avm)
        {
            AccountModel am = new AccountModel();
            if(string.IsNullOrEmpty(avm.Account.Username) ||
                string.IsNullOrEmpty(avm.Account.Password)  ||
                am.Login(avm.Account.Username , avm.Account.Password) == null)
            {
                ViewBag.Error = "Account is Invalid";
                return View("Index");
            }
            SessionPersister.Username = avm.Account.Username;
            return RedirectToAction("Index" , "DashBoard");

           
        } 

        [HttpPost]
        public ActionResult Logout(AccountViewModel avm)
        {
            SessionPersister.Username = string.Empty;
            return RedirectToAction("Index");
        }

Note that we need to specify the view that the system routes after a success login action.

In the configuration file, (web.config) you have to enter the following code indicating the login page's URL.

<system.web>
    <authentication mode="Forms">
      <forms loginUrl="~/Account/Index"></forms>
    </authentication>
    <compilation debug="true" targetFramework="4.6" />
    <httpRuntime targetFramework="4.6" />
  </system.web>

Then we need to implement the security classes that provide the custom authentication and authorization to the Controllers.

For this, I have added a new Folder to the project and named it as Security. After that I have added three classes called CustomPrincipal which is inherited by the interface called IPrincipal , SessionPersister and CustomAuthorizeAttribute which is inherited by the AuthorizeAttribute class.

CustomPrincipal Class

public class CustomPrincipal : IPrincipal
    {
        private Account Account;

        public CustomPrincipal(Account account)
        {
            this.Account = account;
            this.Identity = new GenericIdentity(account.Username);

        }
        public IIdentity Identity
        {
            get;
            set;
        }

        public bool IsInRole(string role)
        {
            var roles = role.Split(new char[] { ',' }).ToList();

            bool isinRole = false;

            var myroles = this.Account.Roles.ToList();

            foreach (var i in roles)
            {
                if (myroles.Contains(i.ToString().Trim()))
                {
                    isinRole = true;
                    break;
                }
            }

            return isinRole;

        }

    }

SessionPersister Class

public static class SessionPersister
    {
        static SessionSecurityEntities db = new SessionSecurityEntities();
        static string usernameSessionvar = "username";

        public static string Username
        {
            get
            {
                if (HttpContext.Current == null)
                    return string.Empty;
                var sessionVar = HttpContext.Current.Session[usernameSessionvar];
                if (sessionVar != null)
                    return sessionVar as string;
                return null;
            }
            set
            {
                HttpContext.Current.Session[usernameSessionvar] = value;
            }

        }
}

AuthorizeAttribute Class 

 public class CustomAuthorizeAttribute : AuthorizeAttribute
    {
        public override void OnAuthorization(AuthorizationContext filterContext)
        {
            if (string.IsNullOrEmpty(SessionPersister.Username))
                filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary
                    (new { controller = "Account", action = "Index" }));
            else
            {
                AccountModel am = new AccountModel();
                CustomPrincipal customPrincipal = new CustomPrincipal(am.FindAccount
                    (SessionPersister.Username));

                if (!customPrincipal.IsInRole(Roles))
                    filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary
                        (new RouteValueDictionary(new { Controller = "AccessDenied", action = "Index" })));

            }
        }
    }
Granting the Permission 
Now we have already implemented the Security part of the accessing the project. Now let's see how to use it with the application.

Let's create another controller called Test for testing the privileges. And later you can apply this with any Controller class or with any Action.

 public class TestController : Controller
    {
        [AllowAnonymous]
        public ActionResult Index()
        {
            return View();
        }

        [CustomAuthorize(Roles = "SuperAdmin")]
        public ActionResult Testing1()
        {
            return View("Testing1"); 
        }

        
[CustomAuthorize(Roles = "SuperAdmin, Admin")]
        public ActionResult Testing2()
        {
            return View("Testing2");
        }

       
 [CustomAuthorize(Roles = "SuperAdmin , Admin, Employee")]
        public ActionResult Testing3()
        {
            return View("Testing3");
        }
    }

(With this Controller class you need to Add Views to each and every action.)

At the same time we have to implement another controller called AccessDenied and add a view to it too.  Then this View will be loaded for the unauthorized accesses of the users. 

I hope this post will be helpful. 
Byee :)