Introduction
In this blog, we'll show you how to make your authentication application. Authentication and authorization are indispensable aspects for any website project to grant users access based on their roles, as you may know. To build custom authentication, we utilize the membership provider class to check user credentials (username and password) and the role provider class to validate user authorization based on roles.
Our authentication system's scenario is as follows:
-
After the user enters his or her credentials (Login and Password), we must use the ValidateUser method from our custom membership provider class.
-
We need to choose a bespoke membership provider that will be utilized. We must make this change within the Web.config file.
-
When a user is successfully authenticated, the Authorize Attribute filter is automatically invoked to determine whether the user has access to the requested resource, and the role provider is the class that is responsible for doing so depending on the user role.
-
Note that we must also define a role provider in the Web.config file.
Create Your MVC application
First, open the Visual Studio and select File -> New Project
This will pop up the New Project window. Select ASP.NET Web Application(.NET Framework) and then click OK.
Now, give the name of the project like CustomAuthenticationMVC, and select the appropriate location where you want to save your project and then click on the create button to create your project.
Then, choose MVC and click on create to add the project.
We will establish a database using the entity framework (Code first approach) once our project is created.
SQL Database part
Entity framework takes a variety of approaches to map databases, including database first, model first, and code first.
To construct our database, simply follow the steps below. First and foremost, we'll establish a folder called Data Access.
To accomplish this, right-click on the project name in Solution Explorer >> Add >> New Folder.
Then we'll add the User and Role entities.
Example
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace CustomAuthenticationMVC.DataAccess { public class User { public int UserId { get; set; } public string Username { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string Email { get; set; } public string Password { get; set; } public bool IsActive { get; set; } public Guid ActivationCode { get; set; } public virtual ICollectionRoles { get; set; } } }
Example
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace CustomAuthenticationMVC.DataAccess { public class Role { public int RoleId { get; set; } public string RoleName { get; set; } public virtual ICollectionUsers { get; set; } } }
As the last step, we're going to add our AuthenticationDB context, which will allow us to access database data. Normally, the context class derives from the dbcontext class.
Example
using CustomAuthenticationMVC.DataAccess; using System; using System.Collections.Generic; using System.Data.Entity; using System.Linq; using System.Web; using System.Web.Configuration; namespace CustomAuthenticationMVC.DataAccess { public class AuthenticationDB : DbContext { public AuthenticationDB() :base("AuthenticationDB") { } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.EntityNote() .HasMany(u => u.Roles) .WithMany(r => r.Users) .Map(m => { m.ToTable("UserRoles"); m.MapLeftKey("UserId"); m.MapRightKey("RoleId"); }); } public DbSet Users { get; set; } public DbSet Roles { get; set; } } }
Make sure you've included your database's connection string in the Web.config file.
Now enter the following command into the Package Manager console.
Read More: Generate Thumbnail Using Asp.net Mvc
Enable-migrations
After performing above aspects, now we're ready to start building our database. Executes the commands in the order listed below.
add-migration "initial_migration"
update-database –verbose
As you can see, all of the tables have been successfully added.
Implementing Membership Provider and Role Provider
Let's get started with the custom MemberShip Provider.
The first step is to construct the CustomMembership class, which should inherit from MembershipProvider.
After that, we'll override the following methods based on our requirements:
- ValidateUser is a function that takes two parameters: username and password, and checks whether the user exists or not.
- The GetUser method is in charge of returning user info based on the username input.
- GetUserNameByEmail takes an email address as an input and returns a username.
Example
using CustomAuthenticationMVC.DataAccess; using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Security; namespace CustomAuthenticationMVC.CustomAuthentication { public class CustomMembership : MembershipProvider { public override bool ValidateUser(string username, string password) { if(string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password)) { return false; } using (AuthenticationDB dbContext = new AuthenticationDB()) { var user = (from us in dbContext.Users where string.Compare(username, us.Username, StringComparison.OrdinalIgnoreCase) == 0 && string.Compare(password, us.Password, StringComparison.OrdinalIgnoreCase) == 0 && us.IsActive == true select us).FirstOrDefault(); return (user != null) ? true : false; } } password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) { throw new NotImplementedException(); } public override MembershipUser GetUser(string username, bool userIsOnline) { using (AuthenticationDB dbContext = new AuthenticationDB()) { var user = (from us in dbContext.Users where string.Compare(username, us.Username, StringComparison.OrdinalIgnoreCase) == 0 select us).FirstOrDefault(); if(user == null) { return null; } var selectedUser = new CustomMembershipUser(user); return selectedUser; } } public override string GetUserNameByEmail(string email) { using (AuthenticationDB dbContext = new DataAccess.AuthenticationDB()) { string username = (from u in dbContext.Users where string.Compare(email, u.Email) == 0 select u.Username).FirstOrDefault(); return !string.IsNullOrEmpty(username) ? username : string.Empty; } } public override string ApplicationName { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } } public override bool EnablePasswordReset { get { throw new NotImplementedException(); } } public override bool EnablePasswordRetrieval { get { throw new NotImplementedException(); } } public override int MaxInvalidPasswordAttempts { get { throw new NotImplementedException(); } } public override int MinRequiredNonAlphanumericCharacters { get { throw new NotImplementedException(); } } public override int MinRequiredPasswordLength { get { throw new NotImplementedException(); } } public override int PasswordAttemptWindow { get { throw new NotImplementedException(); } } public override MembershipPasswordFormat PasswordFormat { get { throw new NotImplementedException(); } } public override string PasswordStrengthRegularExpression { get { throw new NotImplementedException(); } } public override bool RequiresQuestionAndAnswer { get { throw new NotImplementedException(); } } public override bool RequiresUniqueEmail { get { throw new NotImplementedException(); } } public override bool ChangePassword(string username, string oldPassword, string newPassword) { throw new NotImplementedException(); } public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer) { throw new NotImplementedException(); } public override bool DeleteUser(string username, bool deleteAllRelatedData) { throw new NotImplementedException(); } public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords) { throw new NotImplementedException(); } public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) { throw new NotImplementedException(); } public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords) { throw new NotImplementedException(); } public override int GetNumberOfUsersOnline() { throw new NotImplementedException(); } public override string GetPassword(string username, string answer) { throw new NotImplementedException(); } public override MembershipUser GetUser(object providerUserKey, bool userIsOnline) { throw new NotImplementedException(); } public override string ResetPassword(string username, string answer) { throw new NotImplementedException(); } public override bool UnlockUser(string userName) { throw new NotImplementedException(); } public override void UpdateUser(MembershipUser user) { throw new NotImplementedException(); } } }
As you can see, MembershipProvider offers a lot of methods like Create User, ChangePassword, GetPassword, and so on, but we only need ValidateUser, GetUser, and GetUserNameByEmail.
Here we can point out that the GetUser method makes use of the CustomMemberShipUser class to retrieve only the information we require about the user.
Example
using System; using CustomAuthenticationMVC.DataAccess; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Security; namespace CustomAuthenticationMVC.CustomAuthentication { public class CustomMembershipUser : MembershipUser { #region User Properties public int UserId { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public ICollectionRoles { get; set; } #endregion public CustomMembershipUser(User user):base("CustomMembership", user.Username, user.UserId, user.Email, string.Empty, string.Empty, true, false, DateTime.Now, DateTime.Now, DateTime.Now, DateTime.Now, DateTime.Now) { UserId = user.UserId; FirstName = user.FirstName; LastName = user.LastName; Roles = user.Roles; } } }
The second step, as previously said, is to include our customerMembership in the Web.config file. Now we'll update the web.config file and add the following snippet of code.
We'll now use Custom Role Provider to implement it.
In this case, we'll construct a CustomRole class that inherits from RoleProvider and then override the GetRolesForUser and IsUserInRole methods.
Example
using CustomAuthenticationMVC.DataAccess; using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Security; namespace CustomAuthenticationMVC.CustomAuthentication { public class CustomRole : RoleProvider { public override bool IsUserInRole(string username, string roleName) { var userRoles = GetRolesForUser(username); return userRoles.Contains(roleName); } public override string[] GetRolesForUser(string username) { if (!HttpContext.Current.User.Identity.IsAuthenticated) { return null; } var userRoles = new string[] { }; using (AuthenticationDB dbContext = new AuthenticationDB()) { var selectedUser = (from us in dbContext.Users.Include("Roles") where string.Compare(us.Username, username, StringComparison.OrdinalIgnoreCase) == 0 select us).FirstOrDefault(); if(selectedUser != null) { userRoles = new[] { selectedUser.Roles.Select(r=>r.RoleName).ToString() }; } return userRoles.ToArray(); } } public override string ApplicationName { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } } public override void AddUsersToRoles(string[] usernames, string[] roleNames) { throw new NotImplementedException(); } public override void CreateRole(string roleName) { throw new NotImplementedException(); } public override bool DeleteRole(string roleName, bool throwOnPopulatedRole) { throw new NotImplementedException(); } public override string[] FindUsersInRole(string roleName, string usernameToMatch) { throw new NotImplementedException(); } public override string[] GetAllRoles() { throw new NotImplementedException(); } public override string[] GetUsersInRole(string roleName) { throw new NotImplementedException(); } public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames) { throw new NotImplementedException(); } public override bool RoleExists(string roleName) { throw new NotImplementedException(); } } }
Note that the GetRolesForUser method takes a username as a parameter and returns all roles associated with that username. The IsUserInRole method takes two parameters: username and rolename, and verifies whether the user has a role that grants him access to the desired resource.
Want to Hire Trusted .Net Development Company ?
After that, add the following code snippet to the web.config file.
Custom principals and identities are now being implemented. We have a user property that contains basic user data by default to receive user information from HTTP requests. User information is accessed deeply using the IPrincipal interface. In reality, the Identity property of this interface encompasses all user data.
As previously stated, the user property only contains basic user information; however, the goal is to expand this property to include more useful data.
Now, we'll develop the CustomPrincipal class, which will inherit from the IPrincipal interface.
Example
using System; using System.Collections.Generic; using System.Linq; using System.Security.Principal; using System.Web; namespace CustomAuthenticationMVC.CustomAuthentication { public class CustomPrincipal : IPrincipal { #region Identity Properties public int UserId { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string Email { get; set; } public string[] Roles { get; set; } #endregion public IIdentity Identity { get; private set; } public bool IsInRole(string role) { if (Roles.Any(r => role.Contains(r))) { return true; } else { return false; } } public CustomPrincipal(string username) { Identity = new GenericIdentity(username); } } }Note
We'll add the following code snippet within Global.asax to replace the default user property from HttpContext.
Example
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e) { HttpCookie authCookie = Request.Cookies["Cookie1"]; if (authCookie != null) { FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value); var serializeModel = JsonConvert.DeserializeObject(authTicket.UserData); CustomPrincipal principal = new CustomPrincipal(authTicket.Name); principal.UserId = serializeModel.UserId; principal.FirstName = serializeModel.FirstName; principal.LastName = serializeModel.LastName; principal.Roles = serializeModel.RoleName.ToArray (); HttpContext.Current.User = principal; } }
Create a controller
Following the implementation of Custom Membership Provider and Custom Role Provider, I think it is now time to design an Account Controller with all of the required actions to assist us in authenticating users.
We're going to make a controller now. Right-click the controllers folder> > Add >> Controller>> MVC 5 Controller - Empty>> Add. Name the controller “AccountController” in the following dialogue, then click on the Add to add controller successfully.
Example
using CustomAuthenticationMVC.CustomAuthentication; using CustomAuthenticationMVC.DataAccess; using CustomAuthenticationMVC.Models; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Mail; using System.Web; using System.Web.Mvc; using System.Web.Security; namespace CustomAuthenticationMVC.Controllers { [AllowAnonymous] public class AccountController : Controller { public ActionResult Index() { return View(); } [HttpGet] public ActionResult Login(string ReturnUrl = "") { if (User.Identity.IsAuthenticated) { return LogOut(); } ViewBag.ReturnUrl = ReturnUrl; return View(); } [HttpPost] public ActionResult Login(LoginView loginView, string ReturnUrl = "") { if (ModelState.IsValid) { if (Membership.ValidateUser(loginView.UserName, loginView.Password)) { var user = (CustomMembershipUser)Membership.GetUser(loginView.UserName, false); if (user != null) { CustomSerializeModel userModel = new Models.CustomSerializeModel() { UserId = user.UserId, FirstName = user.FirstName, LastName = user.LastName, RoleName = user.Roles.Select(r => r.RoleName).ToList() }; string userData = JsonConvert.SerializeObject(userModel); FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket ( 1, loginView.UserName, DateTime.Now, DateTime.Now.AddMinutes(15), false, userData ); string enTicket = FormsAuthentication.Encrypt(authTicket); HttpCookie faCookie = new HttpCookie("Cookie1", enTicket); Response.Cookies.Add(faCookie); } if (Url.IsLocalUrl(ReturnUrl)) { return Redirect(ReturnUrl); } else { return RedirectToAction("Index"); } } } ModelState.AddModelError("", "Something Wrong : Username or Password invalid ^_^ "); return View(loginView); } [HttpGet] public ActionResult Registration() { return View(); } [HttpPost] public ActionResult Registration(RegistrationView registrationView) { bool statusRegistration = false; string messageRegistration = string.Empty; if (ModelState.IsValid) { string userName = Membership.GetUserNameByEmail(registrationView.Email); if (!string.IsNullOrEmpty(userName)) { ModelState.AddModelError("Warning Email", "Sorry: Email already Exists"); return View(registrationView); } //Save User Data using (AuthenticationDB dbContext = new AuthenticationDB()) { var user = new User() { Username = registrationView.Username, FirstName = registrationView.FirstName, LastName = registrationView.LastName, Email = registrationView.Email, Password = registrationView.Password, ActivationCode = Guid.NewGuid(), }; dbContext.Users.Add(user); dbContext.SaveChanges(); } VerificationEmail(registrationView.Email, registrationView.ActivationCode.ToString()); messageRegistration = "Your account has been created successfully. ^_^"; statusRegistration = true; } else { messageRegistration = "Something Wrong!"; } ViewBag.Message = messageRegistration; ViewBag.Status = statusRegistration; return View(registrationView); } [HttpGet] public ActionResult ActivationAccount(string id) { bool statusAccount = false; using (AuthenticationDB dbContext = new DataAccess.AuthenticationDB()) { var userAccount = dbContext.Users.Where(u => u.ActivationCode.ToString().Equals(id)).FirstOrDefault(); if (userAccount != null) { userAccount.IsActive = true; dbContext.SaveChanges(); statusAccount = true; } else { ViewBag.Message = "Something Wrong !!"; } } ViewBag.Status = statusAccount; return View(); } public ActionResult LogOut() { HttpCookie cookie = new HttpCookie("Cookie1", ""); cookie.Expires = DateTime.Now.AddYears(-1); Response.Cookies.Add(cookie); FormsAuthentication.SignOut(); return RedirectToAction("Login", "Account", null); } [NonAction] public void VerificationEmail(string email, string activationCode) { var url = string.Format("/Account/ActivationAccount/{0}", activationCode); var link = Request.Url.AbsoluteUri.Replace(Request.Url.PathAndQuery, url); var fromEmail = new MailAddress("mehdi.rami2012@gmail.com", "Activation Account - AKKA"); var toEmail = new MailAddress(email); var fromEmailPassword = "******************"; string subject = "Activation Account !"; string body = " Please click on the following link in order to activate your account" + " Activation Account ! "; var smtp = new SmtpClient { Host = "smtp.gmail.com", Port = 587, EnableSsl = true, DeliveryMethod = SmtpDeliveryMethod.Network, UseDefaultCredentials = false, Credentials = new NetworkCredential(fromEmail.Address, fromEmailPassword) }; using (var message = new MailMessage(fromEmail, toEmail) { Subject = subject, Body = body, IsBodyHtml = true }) smtp.Send(message); } } }
Account controller has three major actions, as you can see above.
- Check to see if the person who is going to create a new account has already been created. To do so, we'll use the CustomMembershipProvider's GetUserNameByEmail function.
- After that, we'll save user information.
- We must activate the user account by sending a verification email to the user, informing him that he must activate his account by clicking on the activation link.
- Login action takes a loginView model as a parameter, which has a username and password properties, and then uses the ValidateUser method from custom Membership to check user credentials. If user validation is true, the GetUser function is used to retrieve user data.
- Following that, we'll create an authentication ticket that will be encrypted using the term FormsAuthentication. Finally, we'll encrypt (authTicket) and create a faCookie object with our ticket's encrypted value as the value.
- The registration action is used to create a new account for a user. This action will do three things on a deep level:
- The LogOut action allows the user to log out of his or her session, as the name suggests.
We must now add account views for login, registration, and activation.
Example
@model CustomAuthenticationMVC.Models.LoginView @{ ViewBag.Title = "Login"; }Login
@using (Html.BeginForm(null, null, new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post)) { @Html.AntiForgeryToken()}LoginView
@Html.ValidationSummary(true, "", new { @class = "text-danger" })@Html.LabelFor(model => model.UserName, htmlAttributes: new { @class = "control-label col-md-2" })@Html.EditorFor(model => model.UserName, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.UserName, "", new { @class = "text-danger" })@Html.LabelFor(model => model.Password, htmlAttributes: new { @class = "control-label col-md-2" })@Html.EditorFor(model => model.Password, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Password, "", new { @class = "text-danger" })@Html.LabelFor(model => model.RememberMe, htmlAttributes: new { @class = "control-label col-md-2" })@Html.EditorFor(model => model.RememberMe) @Html.ValidationMessageFor(model => model.RememberMe, "", new { @class = "text-danger" })@Html.ActionLink("Back to List", "Index")
Example
@model CustomAuthenticationMVC.Models.RegistrationView @{ ViewBag.Title = "Registration"; }Registration
@if (ViewBag.Status != null && Convert.ToBoolean(ViewBag.Status)) { if (ViewBag.Message != null) {Success! @ViewBag.Message} } else { using (Html.BeginForm()) { @Html.AntiForgeryToken()if(ViewBag.Message != null) {RegistrationView
@Html.ValidationSummary(true, "", new { @class = "text-danger" })@Html.LabelFor(model => model.Username, htmlAttributes: new { @class = "control-label col-md-2" })@Html.EditorFor(model => model.Username, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Username, "", new { @class = "text-danger" })@Html.LabelFor(model => model.FirstName, htmlAttributes: new { @class = "control-label col-md-2" })@Html.EditorFor(model => model.FirstName, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.FirstName, "", new { @class = "text-danger" })@Html.LabelFor(model => model.LastName, htmlAttributes: new { @class = "control-label col-md-2" })@Html.EditorFor(model => model.LastName, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.LastName, "", new { @class = "text-danger" })@Html.LabelFor(model => model.Email, htmlAttributes: new { @class = "control-label col-md-2" })@Html.EditorFor(model => model.Email, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Email, "", new { @class = "text-danger" }) @Html.ValidationMessage("ErrorEmail", new { @class = "text-danger" })@Html.LabelFor(model => model.Password, htmlAttributes: new { @class = "control-label col-md-2" })@Html.EditorFor(model => model.Password, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Password, "", new { @class = "text-danger" })@Html.LabelFor(model => model.ConfirmPassword, htmlAttributes: new { @class = "control-label col-md-2" })@Html.EditorFor(model => model.ConfirmPassword, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.ConfirmPassword, "", new { @class = "text-danger" })Error! @ViewBag.Message} } }@Html.ActionLink("Login", "Login")@section Scripts{ }
Example
@{ ViewBag.Title = "Activation Account ^_^"; }Activation Account
@if(ViewBag.Status != null && Convert.ToBoolean(ViewBag.Status)) {Success! Your account has been activated successfully.} else {Error!@ViewBag.Message}
Authorization filter
We'll implement a custom authorization filter in this section.
We want to create a filter that denies access to the user controller if the connected user does not have a user role.
Let's take it step by step.
First, make a CustomAuthorizeAttribute class that is derived from AuthorizeAttribute.
Example
using CustomAuthenticationMVC.CustomAuthentication; using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.Routing; namespace CustomAuthenticationMVC.CustomAuthentication { public class CustomAuthorizeAttribute : AuthorizeAttribute { protected virtual CustomPrincipal CurrentUser { get { return HttpContext.Current.User as CustomPrincipal; } } protected override bool AuthorizeCore(HttpContextBase httpContext) { return ((CurrentUser != null && !CurrentUser.IsInRole(Roles)) || CurrentUser == null) ? false : true; } protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) { RedirectToRouteResult routeData = null; if(CurrentUser == null) { routeData = new RedirectToRouteResult (new System.Web.Routing.RouteValueDictionary (new { controller = "Account", action = "Login", } )); } else { routeData = new RedirectToRouteResult (new System.Web.Routing.RouteValueDictionary (new { controller = "Error", action = "AccessDenied" } )); } filterContext.Result = routeData; } } }
Example
using CustomAuthenticationMVC.CustomAuthentication; using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace CustomAuthenticationMVC.Controllers { [CustomAuthorize(Roles = "User")] public class UserController : Controller { // GET: User public ActionResult Index() { return View(); } } }
When a user is successfully authenticated but does not have a user role, we should alert him that his or her access is denied. In the HandleUnauthorizedRequest method, we did something like this.
Example
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace CustomAuthenticationMVC.Controllers { public class ErrorController : Controller { // GET: Error public ActionResult AccessDenied() { return View(); } } }
Example
@{ ViewBag.Title = "AccessDenied"; }AccessDenied
Conclusion
In this blog, we have learned about authentication and authorization in Asp.Net MVC using an example. It will assist you comprehending the importance of validation for any website.
A Detailed Guide on Custom Authentication and Authorization in ASP.NET MVC Table of Content 1. Introduction 2. Create Your MVC application 3. SQL Database part 4. Enable-migrations 5. Implementing Membership Provider and Role Provider 6. Create a controller 7. Authorization filter 8. Conclusion Introduction In this blog, we'll show you how to make your authentication application. Authentication and authorization are indispensable aspects for any website project to grant users access based on their roles, as you may know. To build custom authentication, we utilize the membership provider class to check user credentials (username and password) and the role provider class to validate user authorization based on roles. Our authentication system's scenario is as follows: After the user enters his or her credentials (Login and Password), we must use the ValidateUser method from our custom membership provider class. We need to choose a bespoke membership provider that will be utilized. We must make this change within the Web.config file. When a user is successfully authenticated, the Authorize Attribute filter is automatically invoked to determine whether the user has access to the requested resource, and the role provider is the class that is responsible for doing so depending on the user role. Note that we must also define a role provider in the Web.config file. Create Your MVC application First, open the Visual Studio and select File -> New Project This will pop up the New Project window. Select ASP.NET Web Application(.NET Framework) and then click OK. Figure: Create New ASP.NET Web Application Now, give the name of the project like CustomAuthenticationMVC, and select the appropriate location where you want to save your project and then click on the create button to create your project. Figure : Name the project, choose the path and create a project Then, choose MVC and click on create to add the project. Figure : Select MVC project template and create the project We will establish a database using the entity framework (Code first approach) once our project is created. SQL Database part Entity framework takes a variety of approaches to map databases, including database first, model first, and code first. To construct our database, simply follow the steps below. First and foremost, we'll establish a folder called Data Access. To accomplish this, right-click on the project name in Solution Explorer >> Add >> New Folder. Then we'll add the User and Role entities. Example using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace CustomAuthenticationMVC.DataAccess { public class User { public int UserId { get; set; } public string Username { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string Email { get; set; } public string Password { get; set; } public bool IsActive { get; set; } public Guid ActivationCode { get; set; } public virtual ICollection Roles { get; set; } } } Example using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace CustomAuthenticationMVC.DataAccess { public class Role { public int RoleId { get; set; } public string RoleName { get; set; } public virtual ICollection Users { get; set; } } } As the last step, we're going to add our AuthenticationDB context, which will allow us to access database data. Normally, the context class derives from the dbcontext class. Example using CustomAuthenticationMVC.DataAccess; using System; using System.Collections.Generic; using System.Data.Entity; using System.Linq; using System.Web; using System.Web.Configuration; namespace CustomAuthenticationMVC.DataAccess { public class AuthenticationDB : DbContext { public AuthenticationDB() :base("AuthenticationDB") { } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity() .HasMany(u => u.Roles) .WithMany(r => r.Users) .Map(m => { m.ToTable("UserRoles"); m.MapLeftKey("UserId"); m.MapRightKey("RoleId"); }); } public DbSet Users { get; set; } public DbSet Roles { get; set; } } } Note Make sure you've included your database's connection string in the Web.config file. Now enter the following command into the Package Manager console. Read More: Generate Thumbnail Using Asp.net Mvc Enable-migrations After performing above aspects, now we're ready to start building our database. Executes the commands in the order listed below. add-migration "initial_migration" update-database –verbose Figure: Create a CustomAuthenticationDB by enabling migration As you can see, all of the tables have been successfully added. Implementing Membership Provider and Role Provider Let's get started with the custom MemberShip Provider. The first step is to construct the CustomMembership class, which should inherit from MembershipProvider. After that, we'll override the following methods based on our requirements: ValidateUser is a function that takes two parameters: username and password, and checks whether the user exists or not. The GetUser method is in charge of returning user info based on the username input. GetUserNameByEmail takes an email address as an input and returns a username. Example using CustomAuthenticationMVC.DataAccess; using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Security; namespace CustomAuthenticationMVC.CustomAuthentication { public class CustomMembership : MembershipProvider { public override bool ValidateUser(string username, string password) { if(string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password)) { return false; } using (AuthenticationDB dbContext = new AuthenticationDB()) { var user = (from us in dbContext.Users where string.Compare(username, us.Username, StringComparison.OrdinalIgnoreCase) == 0 && string.Compare(password, us.Password, StringComparison.OrdinalIgnoreCase) == 0 && us.IsActive == true select us).FirstOrDefault(); return (user != null) ? true : false; } } password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) { throw new NotImplementedException(); } public override MembershipUser GetUser(string username, bool userIsOnline) { using (AuthenticationDB dbContext = new AuthenticationDB()) { var user = (from us in dbContext.Users where string.Compare(username, us.Username, StringComparison.OrdinalIgnoreCase) == 0 select us).FirstOrDefault(); if(user == null) { return null; } var selectedUser = new CustomMembershipUser(user); return selectedUser; } } public override string GetUserNameByEmail(string email) { using (AuthenticationDB dbContext = new DataAccess.AuthenticationDB()) { string username = (from u in dbContext.Users where string.Compare(email, u.Email) == 0 select u.Username).FirstOrDefault(); return !string.IsNullOrEmpty(username) ? username : string.Empty; } } public override string ApplicationName { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } } public override bool EnablePasswordReset { get { throw new NotImplementedException(); } } public override bool EnablePasswordRetrieval { get { throw new NotImplementedException(); } } public override int MaxInvalidPasswordAttempts { get { throw new NotImplementedException(); } } public override int MinRequiredNonAlphanumericCharacters { get { throw new NotImplementedException(); } } public override int MinRequiredPasswordLength { get { throw new NotImplementedException(); } } public override int PasswordAttemptWindow { get { throw new NotImplementedException(); } } public override MembershipPasswordFormat PasswordFormat { get { throw new NotImplementedException(); } } public override string PasswordStrengthRegularExpression { get { throw new NotImplementedException(); } } public override bool RequiresQuestionAndAnswer { get { throw new NotImplementedException(); } } public override bool RequiresUniqueEmail { get { throw new NotImplementedException(); } } public override bool ChangePassword(string username, string oldPassword, string newPassword) { throw new NotImplementedException(); } public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer) { throw new NotImplementedException(); } public override bool DeleteUser(string username, bool deleteAllRelatedData) { throw new NotImplementedException(); } public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords) { throw new NotImplementedException(); } public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) { throw new NotImplementedException(); } public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords) { throw new NotImplementedException(); } public override int GetNumberOfUsersOnline() { throw new NotImplementedException(); } public override string GetPassword(string username, string answer) { throw new NotImplementedException(); } public override MembershipUser GetUser(object providerUserKey, bool userIsOnline) { throw new NotImplementedException(); } public override string ResetPassword(string username, string answer) { throw new NotImplementedException(); } public override bool UnlockUser(string userName) { throw new NotImplementedException(); } public override void UpdateUser(MembershipUser user) { throw new NotImplementedException(); } } } As you can see, MembershipProvider offers a lot of methods like Create User, ChangePassword, GetPassword, and so on, but we only need ValidateUser, GetUser, and GetUserNameByEmail. Here we can point out that the GetUser method makes use of the CustomMemberShipUser class to retrieve only the information we require about the user. Example using System; using CustomAuthenticationMVC.DataAccess; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Security; namespace CustomAuthenticationMVC.CustomAuthentication { public class CustomMembershipUser : MembershipUser { #region User Properties public int UserId { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public ICollection Roles { get; set; } #endregion public CustomMembershipUser(User user):base("CustomMembership", user.Username, user.UserId, user.Email, string.Empty, string.Empty, true, false, DateTime.Now, DateTime.Now, DateTime.Now, DateTime.Now, DateTime.Now) { UserId = user.UserId; FirstName = user.FirstName; LastName = user.LastName; Roles = user.Roles; } } } The second step, as previously said, is to include our customerMembership in the Web.config file. Now we'll update the web.config file and add the following snippet of code. We'll now use Custom Role Provider to implement it. In this case, we'll construct a CustomRole class that inherits from RoleProvider and then override the GetRolesForUser and IsUserInRole methods. Example using CustomAuthenticationMVC.DataAccess; using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Security; namespace CustomAuthenticationMVC.CustomAuthentication { public class CustomRole : RoleProvider { public override bool IsUserInRole(string username, string roleName) { var userRoles = GetRolesForUser(username); return userRoles.Contains(roleName); } public override string[] GetRolesForUser(string username) { if (!HttpContext.Current.User.Identity.IsAuthenticated) { return null; } var userRoles = new string[] { }; using (AuthenticationDB dbContext = new AuthenticationDB()) { var selectedUser = (from us in dbContext.Users.Include("Roles") where string.Compare(us.Username, username, StringComparison.OrdinalIgnoreCase) == 0 select us).FirstOrDefault(); if(selectedUser != null) { userRoles = new[] { selectedUser.Roles.Select(r=>r.RoleName).ToString() }; } return userRoles.ToArray(); } } public override string ApplicationName { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } } public override void AddUsersToRoles(string[] usernames, string[] roleNames) { throw new NotImplementedException(); } public override void CreateRole(string roleName) { throw new NotImplementedException(); } public override bool DeleteRole(string roleName, bool throwOnPopulatedRole) { throw new NotImplementedException(); } public override string[] FindUsersInRole(string roleName, string usernameToMatch) { throw new NotImplementedException(); } public override string[] GetAllRoles() { throw new NotImplementedException(); } public override string[] GetUsersInRole(string roleName) { throw new NotImplementedException(); } public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames) { throw new NotImplementedException(); } public override bool RoleExists(string roleName) { throw new NotImplementedException(); } } } Note that the GetRolesForUser method takes a username as a parameter and returns all roles associated with that username. The IsUserInRole method takes two parameters: username and rolename, and verifies whether the user has a role that grants him access to the desired resource. Want to Hire Trusted .Net Development Company ? CONTACT US After that, add the following code snippet to the web.config file. Custom principals and identities are now being implemented. We have a user property that contains basic user data by default to receive user information from HTTP requests. User information is accessed deeply using the IPrincipal interface. In reality, the Identity property of this interface encompasses all user data. As previously stated, the user property only contains basic user information; however, the goal is to expand this property to include more useful data. Now, we'll develop the CustomPrincipal class, which will inherit from the IPrincipal interface. Example using System; using System.Collections.Generic; using System.Linq; using System.Security.Principal; using System.Web; namespace CustomAuthenticationMVC.CustomAuthentication { public class CustomPrincipal : IPrincipal { #region Identity Properties public int UserId { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string Email { get; set; } public string[] Roles { get; set; } #endregion public IIdentity Identity { get; private set; } public bool IsInRole(string role) { if (Roles.Any(r => role.Contains(r))) { return true; } else { return false; } } public CustomPrincipal(string username) { Identity = new GenericIdentity(username); } } } Note We'll add the following code snippet within Global.asax to replace the default user property from HttpContext. Example protected void Application_PostAuthenticateRequest(Object sender, EventArgs e) { HttpCookie authCookie = Request.Cookies["Cookie1"]; if (authCookie != null) { FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value); var serializeModel = JsonConvert.DeserializeObject(authTicket.UserData); CustomPrincipal principal = new CustomPrincipal(authTicket.Name); principal.UserId = serializeModel.UserId; principal.FirstName = serializeModel.FirstName; principal.LastName = serializeModel.LastName; principal.Roles = serializeModel.RoleName.ToArray(); HttpContext.Current.User = principal; } } Create a controller Following the implementation of Custom Membership Provider and Custom Role Provider, I think it is now time to design an Account Controller with all of the required actions to assist us in authenticating users. We're going to make a controller now. Right-click the controllers folder> > Add >> Controller>> MVC 5 Controller - Empty>> Add. Name the controller “AccountController” in the following dialogue, then click on the Add to add controller successfully. Figure: Choose “MVC5 Controller-Empty” from the above controller Figure: Choose “MVC5 Controller-Empty” from the above controller Example using CustomAuthenticationMVC.CustomAuthentication; using CustomAuthenticationMVC.DataAccess; using CustomAuthenticationMVC.Models; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Mail; using System.Web; using System.Web.Mvc; using System.Web.Security; namespace CustomAuthenticationMVC.Controllers { [AllowAnonymous] public class AccountController : Controller { public ActionResult Index() { return View(); } [HttpGet] public ActionResult Login(string ReturnUrl = "") { if (User.Identity.IsAuthenticated) { return LogOut(); } ViewBag.ReturnUrl = ReturnUrl; return View(); } [HttpPost] public ActionResult Login(LoginView loginView, string ReturnUrl = "") { if (ModelState.IsValid) { if (Membership.ValidateUser(loginView.UserName, loginView.Password)) { var user = (CustomMembershipUser)Membership.GetUser(loginView.UserName, false); if (user != null) { CustomSerializeModel userModel = new Models.CustomSerializeModel() { UserId = user.UserId, FirstName = user.FirstName, LastName = user.LastName, RoleName = user.Roles.Select(r => r.RoleName).ToList() }; string userData = JsonConvert.SerializeObject(userModel); FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket ( 1, loginView.UserName, DateTime.Now, DateTime.Now.AddMinutes(15), false, userData ); string enTicket = FormsAuthentication.Encrypt(authTicket); HttpCookie faCookie = new HttpCookie("Cookie1", enTicket); Response.Cookies.Add(faCookie); } if (Url.IsLocalUrl(ReturnUrl)) { return Redirect(ReturnUrl); } else { return RedirectToAction("Index"); } } } ModelState.AddModelError("", "Something Wrong : Username or Password invalid ^_^ "); return View(loginView); } [HttpGet] public ActionResult Registration() { return View(); } [HttpPost] public ActionResult Registration(RegistrationView registrationView) { bool statusRegistration = false; string messageRegistration = string.Empty; if (ModelState.IsValid) { string userName = Membership.GetUserNameByEmail(registrationView.Email); if (!string.IsNullOrEmpty(userName)) { ModelState.AddModelError("Warning Email", "Sorry: Email already Exists"); return View(registrationView); } //Save User Data using (AuthenticationDB dbContext = new AuthenticationDB()) { var user = new User() { Username = registrationView.Username, FirstName = registrationView.FirstName, LastName = registrationView.LastName, Email = registrationView.Email, Password = registrationView.Password, ActivationCode = Guid.NewGuid(), }; dbContext.Users.Add(user); dbContext.SaveChanges(); } VerificationEmail(registrationView.Email, registrationView.ActivationCode.ToString()); messageRegistration = "Your account has been created successfully. ^_^"; statusRegistration = true; } else { messageRegistration = "Something Wrong!"; } ViewBag.Message = messageRegistration; ViewBag.Status = statusRegistration; return View(registrationView); } [HttpGet] public ActionResult ActivationAccount(string id) { bool statusAccount = false; using (AuthenticationDB dbContext = new DataAccess.AuthenticationDB()) { var userAccount = dbContext.Users.Where(u => u.ActivationCode.ToString().Equals(id)).FirstOrDefault(); if (userAccount != null) { userAccount.IsActive = true; dbContext.SaveChanges(); statusAccount = true; } else { ViewBag.Message = "Something Wrong !!"; } } ViewBag.Status = statusAccount; return View(); } public ActionResult LogOut() { HttpCookie cookie = new HttpCookie("Cookie1", ""); cookie.Expires = DateTime.Now.AddYears(-1); Response.Cookies.Add(cookie); FormsAuthentication.SignOut(); return RedirectToAction("Login", "Account", null); } [NonAction] public void VerificationEmail(string email, string activationCode) { var url = string.Format("/Account/ActivationAccount/{0}", activationCode); var link = Request.Url.AbsoluteUri.Replace(Request.Url.PathAndQuery, url); var fromEmail = new MailAddress("mehdi.rami2012@gmail.com", "Activation Account - AKKA"); var toEmail = new MailAddress(email); var fromEmailPassword = "******************"; string subject = "Activation Account !"; string body = " Please click on the following link in order to activate your account" + " Activation Account ! "; var smtp = new SmtpClient { Host = "smtp.gmail.com", Port = 587, EnableSsl = true, DeliveryMethod = SmtpDeliveryMethod.Network, UseDefaultCredentials = false, Credentials = new NetworkCredential(fromEmail.Address, fromEmailPassword) }; using (var message = new MailMessage(fromEmail, toEmail) { Subject = subject, Body = body, IsBodyHtml = true }) smtp.Send(message); } } } Account controller has three major actions, as you can see above. Check to see if the person who is going to create a new account has already been created. To do so, we'll use the CustomMembershipProvider's GetUserNameByEmail function. After that, we'll save user information. We must activate the user account by sending a verification email to the user, informing him that he must activate his account by clicking on the activation link. Login action takes a loginView model as a parameter, which has a username and password properties, and then uses the ValidateUser method from custom Membership to check user credentials. If user validation is true, the GetUser function is used to retrieve user data. Following that, we'll create an authentication ticket that will be encrypted using the term FormsAuthentication. Finally, we'll encrypt (authTicket) and create a faCookie object with our ticket's encrypted value as the value. The registration action is used to create a new account for a user. This action will do three things on a deep level: The LogOut action allows the user to log out of his or her session, as the name suggests. We must now add account views for login, registration, and activation. Example @model CustomAuthenticationMVC.Models.LoginView @{ ViewBag.Title = "Login"; }Login @using (Html.BeginForm(null, null, new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post)) { @Html.AntiForgeryToken() LoginView @Html.ValidationSummary(true, "", new { @class = "text-danger" }) @Html.LabelFor(model => model.UserName, htmlAttributes: new { @class = "control-label col-md-2" }) @Html.EditorFor(model => model.UserName, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.UserName, "", new { @class = "text-danger" }) @Html.LabelFor(model => model.Password, htmlAttributes: new { @class = "control-label col-md-2" }) @Html.EditorFor(model => model.Password, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Password, "", new { @class = "text-danger" }) @Html.LabelFor(model => model.RememberMe, htmlAttributes: new { @class = "control-label col-md-2" }) @Html.EditorFor(model => model.RememberMe) @Html.ValidationMessageFor(model => model.RememberMe, "", new { @class = "text-danger" }) } @Html.ActionLink("Back to List", "Index") Example @model CustomAuthenticationMVC.Models.RegistrationView @{ ViewBag.Title = "Registration"; }Registration @if (ViewBag.Status != null && Convert.ToBoolean(ViewBag.Status)) { if (ViewBag.Message != null) { Success! @ViewBag.Message } } else { using (Html.BeginForm()) { @Html.AntiForgeryToken() RegistrationView @Html.ValidationSummary(true, "", new { @class = "text-danger" }) @Html.LabelFor(model => model.Username, htmlAttributes: new { @class = "control-label col-md-2" }) @Html.EditorFor(model => model.Username, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Username, "", new { @class = "text-danger" }) @Html.LabelFor(model => model.FirstName, htmlAttributes: new { @class = "control-label col-md-2" }) @Html.EditorFor(model => model.FirstName, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.FirstName, "", new { @class = "text-danger" }) @Html.LabelFor(model => model.LastName, htmlAttributes: new { @class = "control-label col-md-2" }) @Html.EditorFor(model => model.LastName, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.LastName, "", new { @class = "text-danger" }) @Html.LabelFor(model => model.Email, htmlAttributes: new { @class = "control-label col-md-2" }) @Html.EditorFor(model => model.Email, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Email, "", new { @class = "text-danger" }) @Html.ValidationMessage("ErrorEmail", new { @class = "text-danger" }) @Html.LabelFor(model => model.Password, htmlAttributes: new { @class = "control-label col-md-2" }) @Html.EditorFor(model => model.Password, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Password, "", new { @class = "text-danger" }) @Html.LabelFor(model => model.ConfirmPassword, htmlAttributes: new { @class = "control-label col-md-2" }) @Html.EditorFor(model => model.ConfirmPassword, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.ConfirmPassword, "", new { @class = "text-danger" }) if(ViewBag.Message != null) { Error! @ViewBag.Message } } } @Html.ActionLink("Login", "Login") @section Scripts{ } Example @{ ViewBag.Title = "Activation Account ^_^"; }Activation Account @if(ViewBag.Status != null && Convert.ToBoolean(ViewBag.Status)) { Success! Your account has been activated successfully. } else { Error!@ViewBag.Message } Authorization filter We'll implement a custom authorization filter in this section. We want to create a filter that denies access to the user controller if the connected user does not have a user role. Let's take it step by step. First, make a CustomAuthorizeAttribute class that is derived from AuthorizeAttribute. Example using CustomAuthenticationMVC.CustomAuthentication; using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.Routing; namespace CustomAuthenticationMVC.CustomAuthentication { public class CustomAuthorizeAttribute : AuthorizeAttribute { protected virtual CustomPrincipal CurrentUser { get { return HttpContext.Current.User as CustomPrincipal; } } protected override bool AuthorizeCore(HttpContextBase httpContext) { return ((CurrentUser != null && !CurrentUser.IsInRole(Roles)) || CurrentUser == null) ? false : true; } protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) { RedirectToRouteResult routeData = null; if(CurrentUser == null) { routeData = new RedirectToRouteResult (new System.Web.Routing.RouteValueDictionary (new { controller = "Account", action = "Login", } )); } else { routeData = new RedirectToRouteResult (new System.Web.Routing.RouteValueDictionary (new { controller = "Error", action = "AccessDenied" } )); } filterContext.Result = routeData; } } } Example using CustomAuthenticationMVC.CustomAuthentication; using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace CustomAuthenticationMVC.Controllers { [CustomAuthorize(Roles = "User")] public class UserController : Controller { // GET: User public ActionResult Index() { return View(); } } } When a user is successfully authenticated but does not have a user role, we should alert him that his or her access is denied. In the HandleUnauthorizedRequest method, we did something like this. Example using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace CustomAuthenticationMVC.Controllers { public class ErrorController : Controller { // GET: Error public ActionResult AccessDenied() { return View(); } } } Example @{ ViewBag.Title = "AccessDenied"; }AccessDenied Conclusion In this blog, we have learned about authentication and authorization in Asp.Net MVC using an example. It will assist you comprehending the importance of validation for any website.
Build Your Agile Team