Custom Authentication With ASP.NET MVC

Introduction

In this article, we will demonstrate how we can create custom authentication app. As you know, authentication and authorization in a website project are still very important to give access to the users based on their roles. For building custom authentication, we use membership provider class which is able to check the user credentials (username & password) and role provider class that is used to verify the user authorization based on his/her roles.

Finally, I'd like to mention that we are using ASP.NET MVC framework in order to build our system. I hope you will like it.

  • Overview.
  • Prerequisites.
  • Create MVC application.
  • Create a database (Using Entity Framework Code First).
  • Implementing Membership provider and role provider.
  • Create controller.
  • Add Authorization filter.

Overview

The scenario of our authentication system is as follows -
  1. The user will provide his/her credentials data (Login & Password) then we need to call ValidateUser method which is defined within our custom membership provider class. TValidateUser method will return true or false value to see if the user already exists from database or not.

  2. Notice that, we must specify custom membership provider which will be used. This update we need to do it within Web.config file.

  3. When the user is authenticated successfully, Authorize Attribute filter will be invoked automatically to check if the user has access or not for requested resource and role provider is the class that is responsible to do that based on user role.

  4. Note, we must also specify role provider which will be used within Web.config file.
Prerequisites

Make sure you have installed Visual Studio 2015 (.Net Framework 4.5.2) and SQL Server.

Create your MVC application

Open Visual Studio and select File >> New Project.

The "New Project" window will pop up. Select ASP.NET Web Application (.NET Framework), name your project, and click OK.
 
 

Next, new dialog will pop up for selecting the template. We are going choose Empty template and click Ok.

 
 
Once our project is created, we will create database using entity framework (Code first approach).
 
SQL Database part

As you know, entity framework has a different approach to map database such as database first, model first, and code first. In this article, I’d like to show you how we can create database using code first approach.

Let’s follow the steps below to create our database. Before of all, we are going create Data Access folder.

To do that. From solution explorer >> right click on project name >> Add >> New Folder.

After that, we are adding User and Role entities respectively.
 
User.cs
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Web;  
  5.   
  6. namespace CustomAuthenticationMVC.DataAccess  
  7. {  
  8.     public class User  
  9.     {  
  10.         public int UserId { getset; }  
  11.         public string Username { getset; }  
  12.         public string FirstName { getset; }  
  13.         public string LastName { getset; }  
  14.         public string Email { getset; }  
  15.         public string Password { getset; }  
  16.         public bool IsActive { getset; }  
  17.         public Guid ActivationCode { getset; }  
  18.         public virtual ICollection<Role> Roles { getset; }  
  19.   
  20.     }  
  21. }  
 Role.cs
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Web;  
  5.   
  6. namespace CustomAuthenticationMVC.DataAccess  
  7. {  
  8.     public class Role  
  9.     {  
  10.         public int RoleId { getset; }  
  11.         public string RoleName { getset; }  
  12.         public virtual ICollection<User> Users { getset; }  
  13.     }  
  14. }  
As final step. We are adding our AuthenticationDB context which will help us to access data from database. Usually context inherits from dbcontext class.
 
AuthenticationDB.cs 
  1. using CustomAuthenticationMVC.DataAccess;  
  2. using System;  
  3. using System.Collections.Generic;  
  4. using System.Data.Entity;  
  5. using System.Linq;  
  6. using System.Web;  
  7. using System.Web.Configuration;  
  8.   
  9. namespace CustomAuthenticationMVC.DataAccess  
  10. {  
  11.     public class AuthenticationDB : DbContext  
  12.     {  
  13.         public AuthenticationDB()  
  14.             :base("AuthenticationDB")  
  15.         {  
  16.   
  17.         }  
  18.   
  19.         protected override void OnModelCreating(DbModelBuilder modelBuilder)  
  20.         {  
  21.             modelBuilder.Entity<User>()  
  22.                 .HasMany(u => u.Roles)  
  23.                 .WithMany(r => r.Users)  
  24.                 .Map(m =>  
  25.                 {  
  26.                     m.ToTable("UserRoles");  
  27.                     m.MapLeftKey("UserId");  
  28.                     m.MapRightKey("RoleId");  
  29.                 });  
  30.         }  
  31.   
  32.         public DbSet<User> Users { getset; }  
  33.         public DbSet<Role> Roles { getset; }  
  34.     }  
  35. }  
Note

Make sure that you have added connection string of your database in Web.config file.
  1. <connectionStrings>  
  2.     <add name="AuthenticationDB" connectionString=" Data Source=.;Initial Catalog=CustomAuthenticationDB;Integrated Security=True" providerName="System.Data.SqlClient" />  
  3.   </connectionStrings>  
Now, Open Package Manager console and type the following command.

Enable-migrations

After that, I can see that we are ready to create our database. Run the following commands respectively.

add-migration "initial_migration"
update-database –verbose
 
 
 
As you can see above, all the tables have been added successfully.

Implementing Membership Provider and Role Provider
 
Let's start implementing custom MemberShip Provider.

Now, first thing that we need to do is to create CustomMembership class that should inherits from MembershipProvider.

After doing that, according to our requirement, we are going to override the following methods,
  • ValidateUser which takes username and password as parameters and test simply if user exists or not.
  • GetUser is responsible to return user data based on username parameter.
  • GetUserNameByEmail accepts email as parameter, and return username as result. 
CustomMembership.cs 
  1. using CustomAuthenticationMVC.DataAccess;  
  2. using System;  
  3. using System.Collections.Generic;  
  4. using System.Linq;  
  5. using System.Web;  
  6. using System.Web.Security;  
  7.   
  8. namespace CustomAuthenticationMVC.CustomAuthentication  
  9. {  
  10.     public class CustomMembership : MembershipProvider  
  11.     {  
  12.   
  13.   
  14.         /// <summary>  
  15.         ///   
  16.         /// </summary>  
  17.         /// <param name="username"></param>  
  18.         /// <param name="password"></param>  
  19.         /// <returns></returns>  
  20.         public override bool ValidateUser(string username, string password)  
  21.         {  
  22.             if(string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))  
  23.             {  
  24.                 return false;  
  25.             }  
  26.   
  27.             using (AuthenticationDB dbContext = new AuthenticationDB())  
  28.             {  
  29.                 var user = (from us in dbContext.Users  
  30.                             where string.Compare(username, us.Username, StringComparison.OrdinalIgnoreCase) == 0  
  31.                             && string.Compare(password, us.Password, StringComparison.OrdinalIgnoreCase) == 0  
  32.                             && us.IsActive == true  
  33.                             select us).FirstOrDefault();  
  34.   
  35.                 return (user != null) ? true : false;  
  36.             }  
  37.         }  
  38.   
  39.         /// <summary>  
  40.         ///   
  41.         /// </summary>  
  42.         /// <param name="username"></param>  
  43.         /// <param name="password"></param>  
  44.         /// <param name="email"></param>  
  45.         /// <param name="passwordQuestion"></param>  
  46.         /// <param name="passwordAnswer"></param>  
  47.         /// <param name="isApproved"></param>  
  48.         /// <param name="providerUserKey"></param>  
  49.         /// <param name="status"></param>  
  50.         /// <returns></returns>  
  51.         public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)  
  52.         {  
  53.             throw new NotImplementedException();  
  54.         }  
  55.   
  56.         /// <summary>  
  57.         ///   
  58.         /// </summary>  
  59.         /// <param name="username"></param>  
  60.         /// <param name="userIsOnline"></param>  
  61.         /// <returns></returns>  
  62.         public override MembershipUser GetUser(string username, bool userIsOnline)  
  63.         {  
  64.             using (AuthenticationDB dbContext = new AuthenticationDB())  
  65.             {  
  66.                 var user = (from us in dbContext.Users  
  67.                             where string.Compare(username, us.Username, StringComparison.OrdinalIgnoreCase) == 0  
  68.                             select us).FirstOrDefault();  
  69.   
  70.                 if(user == null)  
  71.                 {  
  72.                     return null;  
  73.                 }  
  74.                 var selectedUser = new CustomMembershipUser(user);  
  75.   
  76.                 return selectedUser;  
  77.             }  
  78.         }  
  79.   
  80.         public override string GetUserNameByEmail(string email)  
  81.         {  
  82.             using (AuthenticationDB dbContext = new DataAccess.AuthenticationDB())  
  83.             {  
  84.                 string username = (from u in dbContext.Users  
  85.                                    where string.Compare(email, u.Email) == 0  
  86.                                    select u.Username).FirstOrDefault();  
  87.   
  88.                 return !string.IsNullOrEmpty(username) ? username : string.Empty;  
  89.             }  
  90.         }  
  91.  
  92.         #region Overrides of Membership Provider  
  93.   
  94.         public override string ApplicationName  
  95.         {  
  96.             get  
  97.             {  
  98.                 throw new NotImplementedException();  
  99.             }  
  100.   
  101.             set  
  102.             {  
  103.                 throw new NotImplementedException();  
  104.             }  
  105.         }  
  106.   
  107.         public override bool EnablePasswordReset  
  108.         {  
  109.             get  
  110.             {  
  111.                 throw new NotImplementedException();  
  112.             }  
  113.         }  
  114.   
  115.         public override bool EnablePasswordRetrieval  
  116.         {  
  117.             get  
  118.             {  
  119.                 throw new NotImplementedException();  
  120.             }  
  121.         }  
  122.   
  123.         public override int MaxInvalidPasswordAttempts  
  124.         {  
  125.             get  
  126.             {  
  127.                 throw new NotImplementedException();  
  128.             }  
  129.         }  
  130.   
  131.         public override int MinRequiredNonAlphanumericCharacters  
  132.         {  
  133.             get  
  134.             {  
  135.                 throw new NotImplementedException();  
  136.             }  
  137.         }  
  138.   
  139.         public override int MinRequiredPasswordLength  
  140.         {  
  141.             get  
  142.             {  
  143.                 throw new NotImplementedException();  
  144.             }  
  145.         }  
  146.   
  147.         public override int PasswordAttemptWindow  
  148.         {  
  149.             get  
  150.             {  
  151.                 throw new NotImplementedException();  
  152.             }  
  153.         }  
  154.   
  155.         public override MembershipPasswordFormat PasswordFormat  
  156.         {  
  157.             get  
  158.             {  
  159.                 throw new NotImplementedException();  
  160.             }  
  161.         }  
  162.   
  163.         public override string PasswordStrengthRegularExpression  
  164.         {  
  165.             get  
  166.             {  
  167.                 throw new NotImplementedException();  
  168.             }  
  169.         }  
  170.   
  171.         public override bool RequiresQuestionAndAnswer  
  172.         {  
  173.             get  
  174.             {  
  175.                 throw new NotImplementedException();  
  176.             }  
  177.         }  
  178.   
  179.         public override bool RequiresUniqueEmail  
  180.         {  
  181.             get  
  182.             {  
  183.                 throw new NotImplementedException();  
  184.             }  
  185.         }  
  186.   
  187.         public override bool ChangePassword(string username, string oldPassword, string newPassword)  
  188.         {  
  189.             throw new NotImplementedException();  
  190.         }  
  191.   
  192.         public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)  
  193.         {  
  194.             throw new NotImplementedException();  
  195.         }  
  196.   
  197.         public override bool DeleteUser(string username, bool deleteAllRelatedData)  
  198.         {  
  199.             throw new NotImplementedException();  
  200.         }  
  201.   
  202.         public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)  
  203.         {  
  204.             throw new NotImplementedException();  
  205.         }  
  206.   
  207.         public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)  
  208.         {  
  209.             throw new NotImplementedException();  
  210.         }  
  211.   
  212.         public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)  
  213.         {  
  214.             throw new NotImplementedException();  
  215.         }  
  216.   
  217.         public override int GetNumberOfUsersOnline()  
  218.         {  
  219.             throw new NotImplementedException();  
  220.         }  
  221.   
  222.         public override string GetPassword(string username, string answer)  
  223.         {  
  224.             throw new NotImplementedException();  
  225.         }  
  226.   
  227.         public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)  
  228.         {  
  229.             throw new NotImplementedException();  
  230.         }  
  231.   
  232.         public override string ResetPassword(string username, string answer)  
  233.         {  
  234.             throw new NotImplementedException();  
  235.         }  
  236.   
  237.         public override bool UnlockUser(string userName)  
  238.         {  
  239.             throw new NotImplementedException();  
  240.         }  
  241.   
  242.         public override void UpdateUser(MembershipUser user)  
  243.         {  
  244.             throw new NotImplementedException();  
  245.         }  
  246.  
  247.         #endregion  
  248.     }  
  249. }  
As you can see, MembershipProvider has so many methods such as Create User, ChangePassword, GetPassword ... but for us we need only ValidateUser, GetUser, GetUserNameByEmail methods.

I’d like to point out, GetUser method uses CustomMemberShipUser class to get only what we need as user informations.
 
CustomMemberShipUser.cs 
  1. using System;  
  2. using CustomAuthenticationMVC.DataAccess;  
  3. using System.Collections.Generic;  
  4. using System.Linq;  
  5. using System.Web;  
  6. using System.Web.Security;  
  7.   
  8. namespace CustomAuthenticationMVC.CustomAuthentication  
  9. {  
  10.     public class CustomMembershipUser : MembershipUser  
  11.     {  
  12.         #region User Properties  
  13.   
  14.         public int UserId { getset; }  
  15.         public string FirstName { getset; }  
  16.         public string LastName { getset; }  
  17.         public ICollection<Role> Roles { getset; }  
  18.  
  19.         #endregion  
  20.   
  21.         public CustomMembershipUser(User user):base("CustomMembership", user.Username, user.UserId, user.Email, string.Empty, string.Empty, truefalse, DateTime.Now, DateTime.Now, DateTime.Now, DateTime.Now, DateTime.Now)  
  22.         {  
  23.             UserId = user.UserId;  
  24.             FirstName = user.FirstName;  
  25.             LastName = user.LastName;  
  26.             Roles = user.Roles;  
  27.         }  
  28.     }  
  29. }  
Note, as we mentioned before, the second step consists of adding our customerMembership within Web.config file. This is what we are going to do now, edit web.config file and adding the following code snipped.
  1. <membership defaultProvider="CustomMembership">  
  2.       <providers>  
  3.         <clear/>  
  4.         <add name="CustomMembership"  
  5.              type="CustomAuthenticationMVC.CustomAuthentication.CustomMembership"/>  
  6.       </providers>  
  7.     </membership>  
Now, we will switch to implement Custom Role Provider.

Here, we need to create CustomRole class that should inherits from RoleProvider then we will override GetRolesForUser & IsUserInRole methods respectively.
 
CustomRole.cs 
  1. using CustomAuthenticationMVC.DataAccess;  
  2. using System;  
  3. using System.Collections.Generic;  
  4. using System.Linq;  
  5. using System.Web;  
  6. using System.Web.Security;  
  7.   
  8. namespace CustomAuthenticationMVC.CustomAuthentication  
  9. {  
  10.     public class CustomRole : RoleProvider  
  11.     {  
  12.         /// <summary>  
  13.         ///   
  14.         /// </summary>  
  15.         /// <param name="username"></param>  
  16.         /// <param name="roleName"></param>  
  17.         /// <returns></returns>  
  18.         public override bool IsUserInRole(string username, string roleName)  
  19.         {  
  20.             var userRoles = GetRolesForUser(username);  
  21.             return userRoles.Contains(roleName);  
  22.         }  
  23.   
  24.         /// <summary>  
  25.         ///   
  26.         /// </summary>  
  27.         /// <param name="username"></param>  
  28.         /// <returns></returns>  
  29.         public override string[] GetRolesForUser(string username)  
  30.         {  
  31.             if (!HttpContext.Current.User.Identity.IsAuthenticated)  
  32.             {  
  33.                 return null;  
  34.             }  
  35.   
  36.             var userRoles = new string[] { };  
  37.   
  38.             using (AuthenticationDB dbContext = new AuthenticationDB())  
  39.             {  
  40.                 var selectedUser = (from us in dbContext.Users.Include("Roles")  
  41.                                     where string.Compare(us.Username, username, StringComparison.OrdinalIgnoreCase) == 0  
  42.                                     select us).FirstOrDefault();  
  43.   
  44.                   
  45.                 if(selectedUser != null)  
  46.                 {  
  47.                     userRoles = new[] { selectedUser.Roles.Select(r=>r.RoleName).ToString() };  
  48.                 }  
  49.   
  50.                 return userRoles.ToArray();  
  51.             }  
  52.   
  53.   
  54.         }  
  55.  
  56.  
  57.  
  58.         #region Overrides of Role Provider  
  59.   
  60.         public override string ApplicationName  
  61.         {  
  62.             get  
  63.             {  
  64.                 throw new NotImplementedException();  
  65.             }  
  66.   
  67.             set  
  68.             {  
  69.                 throw new NotImplementedException();  
  70.             }  
  71.         }  
  72.   
  73.         public override void AddUsersToRoles(string[] usernames, string[] roleNames)  
  74.         {  
  75.             throw new NotImplementedException();  
  76.         }  
  77.   
  78.         public override void CreateRole(string roleName)  
  79.         {  
  80.             throw new NotImplementedException();  
  81.         }  
  82.   
  83.         public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)  
  84.         {  
  85.             throw new NotImplementedException();  
  86.         }  
  87.   
  88.         public override string[] FindUsersInRole(string roleName, string usernameToMatch)  
  89.         {  
  90.             throw new NotImplementedException();  
  91.         }  
  92.   
  93.         public override string[] GetAllRoles()  
  94.         {  
  95.             throw new NotImplementedException();  
  96.         }  
  97.   
  98.         public override string[] GetUsersInRole(string roleName)  
  99.         {  
  100.             throw new NotImplementedException();  
  101.         }  
  102.   
  103.   
  104.         public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)  
  105.         {  
  106.             throw new NotImplementedException();  
  107.         }  
  108.   
  109.         public override bool RoleExists(string roleName)  
  110.         {  
  111.             throw new NotImplementedException();  
  112.         }  
  113.  
  114.         #endregion  
  115.     }  
  116. }  
Notice that GetRolesForUser method accepts username as parameter, then it will return all roles of the given username. For IsUserInRole method takes username and rolename as parameters and checks if user has a role that will allow him access to the requested resource.

Next, do not forget editing web.config file and adding the following code snippet.
  1. <roleManager defaultProvider="CustomRole" enabled="true">  
  2.       <providers>  
  3.         <clear/>  
  4.         <add name="CustomRole" type="CustomAuthenticationMVC.CustomAuthentication.CustomRole" />  
  5.       </providers>  
  6.     </roleManager>  
Now, we are implementing custom principal and identity. By default to get user informations from http request, we have user property that contains basics user data. Deeply, user informations is accessed via IPrincipal interface. In fact, this interface has Identity property that encapsulates all user information.

As we mentioned before, user property holds only basic user informations but the idea is to extend this property in order to have more informations which will be helpful.

Now, we are going create CustomPrincipal class that inherits from IPrincipal interface.
 
CustomPrincipal.cs
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Security.Principal;  
  5. using System.Web;  
  6.   
  7. namespace CustomAuthenticationMVC.CustomAuthentication  
  8. {  
  9.     public class CustomPrincipal : IPrincipal  
  10.     {  
  11.         #region Identity Properties  
  12.   
  13.         public int UserId { getset; }  
  14.         public string FirstName { getset; }  
  15.         public string LastName { getset; }  
  16.         public string Email { getset; }  
  17.         public string[] Roles { getset; }  
  18.         #endregion  
  19.   
  20.         public IIdentity Identity  
  21.         {  
  22.             getprivate set;  
  23.         }  
  24.   
  25.         public bool IsInRole(string role)  
  26.         {  
  27.             if (Roles.Any(r => role.Contains(r)))  
  28.             {  
  29.                 return true;  
  30.             }  
  31.             else  
  32.             {  
  33.                 return false;  
  34.             }  
  35.         }  
  36.   
  37.         public CustomPrincipal(string username)  
  38.         {  
  39.             Identity = new GenericIdentity(username);  
  40.         }  
  41.     }  
  42. }  
Note

To replace the default user property from HttpContext. We will add the following code snippet inside Global.asax
  1. protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)  
  2.         {  
  3.             HttpCookie authCookie = Request.Cookies["Cookie1"];  
  4.             if (authCookie != null)  
  5.             {  
  6.                 FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);  
  7.   
  8.                 var serializeModel = JsonConvert.DeserializeObject<CustomSerializeModel>(authTicket.UserData);  
  9.   
  10.                 CustomPrincipal principal = new CustomPrincipal(authTicket.Name);  
  11.   
  12.                 principal.UserId = serializeModel.UserId;  
  13.                 principal.FirstName = serializeModel.FirstName;  
  14.                 principal.LastName = serializeModel.LastName;  
  15.                 principal.Roles = serializeModel.RoleName.ToArray<string>();  
  16.   
  17.                 HttpContext.Current.User = principal;  
  18.             }  
  19.   
  20.         }  
Create a controller

After implementing Custom Membership Provider and Custom Role Provider, I think that the time has come to define Account Controller with all the needed actions which help us authenticating users.

Now, we are going to create a controller. Right click on the controllers folder> > Add >> Controller>> selecting MVC 5 Controller - Empty>> click Add. In the next dialog name the controller AccountController and then click Add.
 
 
 
 
AccountController.cs 
  1. using CustomAuthenticationMVC.CustomAuthentication;  
  2. using CustomAuthenticationMVC.DataAccess;  
  3. using CustomAuthenticationMVC.Models;  
  4. using Newtonsoft.Json;  
  5. using System;  
  6. using System.Collections.Generic;  
  7. using System.Linq;  
  8. using System.Net;  
  9. using System.Net.Mail;  
  10. using System.Web;  
  11. using System.Web.Mvc;  
  12. using System.Web.Security;  
  13.   
  14. namespace CustomAuthenticationMVC.Controllers  
  15. {  
  16.     [AllowAnonymous]  
  17.     public class AccountController : Controller  
  18.     {  
  19.         // GET: Account  
  20.         public ActionResult Index()  
  21.         {  
  22.             return View();  
  23.         }  
  24.   
  25.         [HttpGet]  
  26.         public ActionResult Login(string ReturnUrl = "")  
  27.         {  
  28.             if (User.Identity.IsAuthenticated)  
  29.             {  
  30.                 return LogOut();  
  31.             }  
  32.             ViewBag.ReturnUrl = ReturnUrl;  
  33.             return View();  
  34.         }  
  35.   
  36.         [HttpPost]  
  37.         public ActionResult Login(LoginView loginView, string ReturnUrl = "")  
  38.         {  
  39.             if (ModelState.IsValid)  
  40.             {  
  41.                 if (Membership.ValidateUser(loginView.UserName, loginView.Password))  
  42.                 {  
  43.                     var user = (CustomMembershipUser)Membership.GetUser(loginView.UserName, false);  
  44.                     if (user != null)  
  45.                     {  
  46.                         CustomSerializeModel userModel = new Models.CustomSerializeModel()  
  47.                         {  
  48.                             UserId = user.UserId,  
  49.                             FirstName = user.FirstName,  
  50.                             LastName = user.LastName,  
  51.                             RoleName = user.Roles.Select(r => r.RoleName).ToList()  
  52.                         };  
  53.   
  54.                         string userData = JsonConvert.SerializeObject(userModel);  
  55.                         FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket  
  56.                             (  
  57.                             1, loginView.UserName, DateTime.Now, DateTime.Now.AddMinutes(15), false, userData  
  58.                             );  
  59.   
  60.                         string enTicket = FormsAuthentication.Encrypt(authTicket);  
  61.                         HttpCookie faCookie = new HttpCookie("Cookie1", enTicket);  
  62.                         Response.Cookies.Add(faCookie);  
  63.                     }  
  64.   
  65.                     if (Url.IsLocalUrl(ReturnUrl))  
  66.                     {  
  67.                         return Redirect(ReturnUrl);  
  68.                     }  
  69.                     else  
  70.                     {  
  71.                         return RedirectToAction("Index");  
  72.                     }  
  73.                 }  
  74.             }  
  75.             ModelState.AddModelError("""Something Wrong : Username or Password invalid ^_^ ");  
  76.             return View(loginView);  
  77.         }  
  78.   
  79.         [HttpGet]  
  80.         public ActionResult Registration()  
  81.         {  
  82.             return View();  
  83.         }  
  84.   
  85.         [HttpPost]  
  86.         public ActionResult Registration(RegistrationView registrationView)  
  87.         {  
  88.             bool statusRegistration = false;  
  89.             string messageRegistration = string.Empty;  
  90.   
  91.             if (ModelState.IsValid)  
  92.             {  
  93.                 // Email Verification  
  94.                 string userName = Membership.GetUserNameByEmail(registrationView.Email);  
  95.                 if (!string.IsNullOrEmpty(userName))  
  96.                 {  
  97.                     ModelState.AddModelError("Warning Email""Sorry: Email already Exists");  
  98.                     return View(registrationView);  
  99.                 }  
  100.   
  101.                 //Save User Data   
  102.                 using (AuthenticationDB dbContext = new AuthenticationDB())  
  103.                 {  
  104.                     var user = new User()  
  105.                     {  
  106.                         Username = registrationView.Username,  
  107.                         FirstName = registrationView.FirstName,  
  108.                         LastName = registrationView.LastName,  
  109.                         Email = registrationView.Email,  
  110.                         Password = registrationView.Password,  
  111.                         ActivationCode = Guid.NewGuid(),  
  112.                     };  
  113.   
  114.                     dbContext.Users.Add(user);  
  115.                     dbContext.SaveChanges();  
  116.                 }  
  117.   
  118.                 //Verification Email  
  119.                 VerificationEmail(registrationView.Email, registrationView.ActivationCode.ToString());  
  120.                 messageRegistration = "Your account has been created successfully. ^_^";  
  121.                 statusRegistration = true;  
  122.             }  
  123.             else  
  124.             {  
  125.                 messageRegistration = "Something Wrong!";  
  126.             }  
  127.             ViewBag.Message = messageRegistration;  
  128.             ViewBag.Status = statusRegistration;  
  129.   
  130.             return View(registrationView);  
  131.         }  
  132.   
  133.         [HttpGet]  
  134.         public ActionResult ActivationAccount(string id)  
  135.         {  
  136.             bool statusAccount = false;  
  137.             using (AuthenticationDB dbContext = new DataAccess.AuthenticationDB())  
  138.             {  
  139.                 var userAccount = dbContext.Users.Where(u => u.ActivationCode.ToString().Equals(id)).FirstOrDefault();  
  140.   
  141.                 if (userAccount != null)  
  142.                 {  
  143.                     userAccount.IsActive = true;  
  144.                     dbContext.SaveChanges();  
  145.                     statusAccount = true;  
  146.                 }  
  147.                 else  
  148.                 {  
  149.                     ViewBag.Message = "Something Wrong !!";  
  150.                 }  
  151.   
  152.             }  
  153.             ViewBag.Status = statusAccount;  
  154.             return View();  
  155.         }  
  156.   
  157.         public ActionResult LogOut()  
  158.         {  
  159.             HttpCookie cookie = new HttpCookie("Cookie1""");  
  160.             cookie.Expires = DateTime.Now.AddYears(-1);  
  161.             Response.Cookies.Add(cookie);  
  162.   
  163.             FormsAuthentication.SignOut();  
  164.             return RedirectToAction("Login""Account"null);  
  165.         }  
  166.   
  167.         [NonAction]  
  168.         public void VerificationEmail(string email, string activationCode)  
  169.         {  
  170.             var url = string.Format("/Account/ActivationAccount/{0}", activationCode);  
  171.             var link = Request.Url.AbsoluteUri.Replace(Request.Url.PathAndQuery, url);  
  172.   
  173.             var fromEmail = new MailAddress("[email protected]""Activation Account - AKKA");  
  174.             var toEmail = new MailAddress(email);  
  175.   
  176.             var fromEmailPassword = "******************";  
  177.             string subject = "Activation Account !";  
  178.   
  179.             string body = "<br/> Please click on the following link in order to activate your account" + "<br/><a href='" + link + "'> Activation Account ! </a>";  
  180.   
  181.             var smtp = new SmtpClient  
  182.             {  
  183.                 Host = "smtp.gmail.com",  
  184.                 Port = 587,  
  185.                 EnableSsl = true,  
  186.                 DeliveryMethod = SmtpDeliveryMethod.Network,  
  187.                 UseDefaultCredentials = false,  
  188.                 Credentials = new NetworkCredential(fromEmail.Address, fromEmailPassword)  
  189.             };  
  190.   
  191.             using (var message = new MailMessage(fromEmail, toEmail)  
  192.             {  
  193.                 Subject = subject,  
  194.                 Body = body,  
  195.                 IsBodyHtml = true  
  196.   
  197.             })  
  198.   
  199.                 smtp.Send(message);  
  200.   
  201.         }  
  202.     }  
  203. }  
As you can see above, account controller has three main actions,
  • Login action accepts loginView model as parameter which contains username and password properties, then this action will verify user credentials using ValidateUser method from custom Membership. If user validation is true, we are getting user data based on GetUser method.

    Next, we are creating authentication ticket that should be encrypted using the following expression  FormsAuthentication.Encrypt (authTicket) and finally creating faCookie object that has our ticket encrypted as value.

  • Registration action is used to create new user account. Deeply this action will do three things,

    • Verify if user who would create new account has already been created. To check that, we will use GetUserNameByEmail method from CustomMembershipProvider.
    • Next, we will save user data.
    • We must activate user account by using verification email which will send an email to user and tells him that you should activate your account by clicking on activation link.

  • LogOut action as its name suggests, this action enables user to log out his/her session. 
Now, we need to add login, registration and activation account views.
 
Login.cshtml 
  1. @model CustomAuthenticationMVC.Models.LoginView  
  2.   
  3. @{  
  4.     ViewBag.Title = "Login";  
  5. }  
  6.   
  7. <h2>Login</h2>  
  8.   
  9.   
  10. @using (Html.BeginForm(nullnullnew { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post))  
  11. {  
  12.     @Html.AntiForgeryToken()  
  13.       
  14.     <div class="form-horizontal">  
  15.         <h4>LoginView</h4>  
  16.         <hr />  
  17.         @Html.ValidationSummary(true""new { @class = "text-danger" })  
  18.         <div class="form-group">  
  19.             @Html.LabelFor(model => model.UserName, htmlAttributes: new { @class = "control-label col-md-2" })  
  20.             <div class="col-md-10">  
  21.                 @Html.EditorFor(model => model.UserName, new { htmlAttributes = new { @class = "form-control" } })  
  22.                 @Html.ValidationMessageFor(model => model.UserName, ""new { @class = "text-danger" })  
  23.             </div>  
  24.         </div>  
  25.   
  26.         <div class="form-group">  
  27.             @Html.LabelFor(model => model.Password, htmlAttributes: new { @class = "control-label col-md-2" })  
  28.             <div class="col-md-10">  
  29.                 @Html.EditorFor(model => model.Password, new { htmlAttributes = new { @class = "form-control" } })  
  30.                 @Html.ValidationMessageFor(model => model.Password, ""new { @class = "text-danger" })  
  31.             </div>  
  32.         </div>  
  33.   
  34.         <div class="form-group">  
  35.             @Html.LabelFor(model => model.RememberMe, htmlAttributes: new { @class = "control-label col-md-2" })  
  36.             <div class="col-md-10">  
  37.                 <div class="checkbox">  
  38.                     @Html.EditorFor(model => model.RememberMe)  
  39.                     @Html.ValidationMessageFor(model => model.RememberMe, ""new { @class = "text-danger" })  
  40.                 </div>  
  41.             </div>  
  42.         </div>  
  43.   
  44.         <div class="form-group">  
  45.             <div class="col-md-offset-2 col-md-10">  
  46.                 <input type="submit" value="Log In" class="btn btn-default" />  
  47.             </div>  
  48.         </div>  
  49.     </div>  
  50. }  
  51.   
  52. <div>  
  53.     @Html.ActionLink("Back to List""Index")  
  54. </div>  
  55.   
  56. <script src="~/Scripts/jquery-1.10.2.min.js"></script>  
  57. <script src="~/Scripts/jquery.validate.min.js"></script>  
  58. <script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>  
Registration.cshtml
  1. @model CustomAuthenticationMVC.Models.RegistrationView  
  2.   
  3. @{  
  4.     ViewBag.Title = "Registration";  
  5. }  
  6.   
  7. <h2>Registration</h2>  
  8.   
  9. @if (ViewBag.Status != null && Convert.ToBoolean(ViewBag.Status))  
  10. {  
  11.     if (ViewBag.Message != null)  
  12.     {  
  13.         <div class="alert alert-success">  
  14.             <strong>Success!</strong> @ViewBag.Message  
  15.         </div>  
  16.     }  
  17. }  
  18. else  
  19. {  
  20.     using (Html.BeginForm())  
  21.     {  
  22.         @Html.AntiForgeryToken()  
  23.   
  24.         <div class="form-horizontal">  
  25.             <h4>RegistrationView</h4>  
  26.             <hr />  
  27.             @Html.ValidationSummary(true""new { @class = "text-danger" })  
  28.             <div class="form-group">  
  29.                 @Html.LabelFor(model => model.Username, htmlAttributes: new { @class = "control-label col-md-2" })  
  30.                 <div class="col-md-10">  
  31.                     @Html.EditorFor(model => model.Username, new { htmlAttributes = new { @class = "form-control" } })  
  32.                     @Html.ValidationMessageFor(model => model.Username, ""new { @class = "text-danger" })  
  33.                 </div>  
  34.             </div>  
  35.   
  36.             <div class="form-group">  
  37.                 @Html.LabelFor(model => model.FirstName, htmlAttributes: new { @class = "control-label col-md-2" })  
  38.                 <div class="col-md-10">  
  39.                     @Html.EditorFor(model => model.FirstName, new { htmlAttributes = new { @class = "form-control" } })  
  40.                     @Html.ValidationMessageFor(model => model.FirstName, ""new { @class = "text-danger" })  
  41.                 </div>  
  42.             </div>  
  43.   
  44.             <div class="form-group">  
  45.                 @Html.LabelFor(model => model.LastName, htmlAttributes: new { @class = "control-label col-md-2" })  
  46.                 <div class="col-md-10">  
  47.                     @Html.EditorFor(model => model.LastName, new { htmlAttributes = new { @class = "form-control" } })  
  48.                     @Html.ValidationMessageFor(model => model.LastName, ""new { @class = "text-danger" })  
  49.                 </div>  
  50.             </div>  
  51.   
  52.             <div class="form-group">  
  53.                 @Html.LabelFor(model => model.Email, htmlAttributes: new { @class = "control-label col-md-2" })  
  54.                 <div class="col-md-10">  
  55.                     @Html.EditorFor(model => model.Email, new { htmlAttributes = new { @class = "form-control" } })  
  56.                     @Html.ValidationMessageFor(model => model.Email, ""new { @class = "text-danger" })  
  57.                     @Html.ValidationMessage("ErrorEmail"new { @class = "text-danger" })  
  58.                 </div>  
  59.             </div>  
  60.   
  61.             <div class="form-group">  
  62.                 @Html.LabelFor(model => model.Password, htmlAttributes: new { @class = "control-label col-md-2" })  
  63.                 <div class="col-md-10">  
  64.                     @Html.EditorFor(model => model.Password, new { htmlAttributes = new { @class = "form-control" } })  
  65.                     @Html.ValidationMessageFor(model => model.Password, ""new { @class = "text-danger" })  
  66.                 </div>  
  67.             </div>  
  68.   
  69.             <div class="form-group">  
  70.                 @Html.LabelFor(model => model.ConfirmPassword, htmlAttributes: new { @class = "control-label col-md-2" })  
  71.                 <div class="col-md-10">  
  72.                     @Html.EditorFor(model => model.ConfirmPassword, new { htmlAttributes = new { @class = "form-control" } })  
  73.                     @Html.ValidationMessageFor(model => model.ConfirmPassword, ""new { @class = "text-danger" })  
  74.                 </div>  
  75.             </div>  
  76.   
  77.             <div class="form-group">  
  78.                 <div class="col-md-offset-2 col-md-10">  
  79.                     <input type="submit" value="Create" class="btn btn-default" />  
  80.                 </div>  
  81.             </div>  
  82.         </div>  
  83.   
  84.         if(ViewBag.Message != null)  
  85.         {  
  86.             <div class="alert alert-danger">  
  87.                 <strong>Error!</strong> @ViewBag.Message  
  88.             </div>  
  89.         }  
  90.   
  91.     }  
  92. }  
  93.   
  94. <div>  
  95.     @Html.ActionLink("Login""Login")  
  96. </div>  
  97.   
  98. @section Scripts{  
  99.   
  100. <script src="~/Scripts/jquery.validate.min.js"></script>  
  101. <script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>  
  102. }  
ActivationAccount.cshtml
  1. @{  
  2.     ViewBag.Title = "Activation Account ^_^";  
  3. }  
  4.   
  5. <h2>Activation Account</h2>  
  6.   
  7. @if(ViewBag.Status != null && Convert.ToBoolean(ViewBag.Status))  
  8. {  
  9.     <div class="alert alert-success">  
  10.         <strong>Success!</strong>  Your account has been activated successfully.  
  11.     </div>  
  12. }  
  13. else  
  14. {  
  15.     <div class="alert alert-danger">  
  16.         <strong>Error!</strong>@ViewBag.Message  
  17.     </div>  
  18. }  
Authorization Filter

In this part, we will implement custom authorization filter. What we would like to do is to create filter which restricts access to user controller if the connected user has not user role.

So, let’s follow steps.

Firstly, Create CustomAuthorizeAttribute class which inherits from AuthorizeAttribute.

CustomAuthorizeAttribute.cs
  1. using CustomAuthenticationMVC.CustomAuthentication;  
  2. using System;  
  3. using System.Collections.Generic;  
  4. using System.Linq;  
  5. using System.Web;  
  6. using System.Web.Mvc;  
  7. using System.Web.Routing;  
  8.   
  9. namespace CustomAuthenticationMVC.CustomAuthentication  
  10. {  
  11.     public class CustomAuthorizeAttribute : AuthorizeAttribute  
  12.     {  
  13.         protected virtual CustomPrincipal CurrentUser  
  14.         {  
  15.             get { return HttpContext.Current.User as CustomPrincipal; }  
  16.         }  
  17.   
  18.         protected override bool AuthorizeCore(HttpContextBase httpContext)  
  19.         {  
  20.             return ((CurrentUser != null && !CurrentUser.IsInRole(Roles)) || CurrentUser == null) ? false : true;  
  21.         }  
  22.   
  23.         protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)  
  24.         {  
  25.             RedirectToRouteResult routeData = null;  
  26.   
  27.             if(CurrentUser == null)  
  28.             {  
  29.                 routeData = new RedirectToRouteResult  
  30.                     (new System.Web.Routing.RouteValueDictionary  
  31.                     (new  
  32.                     {  
  33.                         controller = "Account",  
  34.                         action = "Login",  
  35.                     }  
  36.                     ));  
  37.             }  
  38.             else  
  39.             {  
  40.                 routeData = new RedirectToRouteResult  
  41.                 (new System.Web.Routing.RouteValueDictionary  
  42.                  (new  
  43.                  {  
  44.                      controller = "Error",  
  45.                      action = "AccessDenied"  
  46.                  }  
  47.                  ));  
  48.             }  
  49.   
  50.             filterContext.Result = routeData;  
  51.         }  
  52.   
  53.     }  
  54. }  
UserController.cs
  1. using CustomAuthenticationMVC.CustomAuthentication;  
  2. using System;  
  3. using System.Collections.Generic;  
  4. using System.Linq;  
  5. using System.Web;  
  6. using System.Web.Mvc;  
  7.   
  8. namespace CustomAuthenticationMVC.Controllers  
  9. {  
  10.     [CustomAuthorize(Roles = "User")]  
  11.     public class UserController : Controller  
  12.     {  
  13.           
  14.         // GET: User  
  15.         public ActionResult Index()  
  16.         {  
  17.             return View();  
  18.         }  
  19.     }  
  20. }  
When user is authenticated successfully and has not user role, in this case we should inform him that his/her access is denied. This is what we made in HandleUnauthorizedRequest method.
 
ErrorController.cs 
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Web;  
  5. using System.Web.Mvc;  
  6.   
  7. namespace CustomAuthenticationMVC.Controllers  
  8. {  
  9.     public class ErrorController : Controller  
  10.     {  
  11.         // GET: Error  
  12.         public ActionResult AccessDenied()  
  13.         {  
  14.             return View();  
  15.         }  
  16.     }  
  17. }  
AccessDenied.cshtml
  1. @{  
  2.     ViewBag.Title = "AccessDenied";  
  3. }  
  4.   
  5. <h2>AccessDenied</h2>  
Happy Coding!

That’s all. Please send your feedback and queries in comments box.

Next Recommended Readings