BlueLemonCode.Com

Yet Another Tech Blog

Creating custom authentication form in MVC

Introduction

For any small/medium applications, we required to create login functionality and role management. Although, the Memebrship provider of .Net is good option. However, for most of the applications custom authentication mechanism is required due to varying business logic/requirements

In this small I am going to demonstrate how quickly you can create custom authentication form in MVC application

Article Body

Creating authentication mechanism in Asp.net web form and MVC application is fairly simillar at data level. The difference mainly lies at data access layer and model.

Lets first create table in sql server which will hold the user data. The table contains following columns (As a demo i have included only most obvious columns into user table. You may need to have many other)

Now, lets create a sample MVC application project using MVC 3 and Visual studio 2010 web developer express with application type as Internet application and Razor as view engine

By default solution explorer will have following files

Run the application using F5. Note that, default page already has a LogOn link at top right corner; Go to LogOn. We already have Register button. So, we would utilise the Log in and Register controller methods and both the views in this demo. What we need to do now is to create a Model first.

In the solution explorer, right click on Models and go to Add, select Class option. Name the class file as CustomAccountModel.cs. Open the class file. This is where we have to define model class of our Users table. The model class also contain data annotations to define validation attributes, display property.

The model class of AccountModel.cs class (default class available) already have defined model. We will use the most part of it apart from additional column names and couple of changes.

[Bind(Exclude="userid")]
    public class Users
    {
        [Key]
        [ScaffoldColumn(false)]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public string Userid { get; set; }

        [Required]
        [Display(Name = "User name")]
        public string UserName { get; set; }

        [Required]
        [DataType(DataType.EmailAddress)]
        [Display(Name = "Email address")]
        public string EmailAddress { get; set; }

        [Required]
        [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
        [DataType(DataType.Password)]
        [Display(Name = "Password")]
        public string Password { get; set; }

        [DataType(DataType.Text)]
        [Display(Name = "Full Name")]
        public string FullName { get; set; }

        [DataType(DataType.Text)]
        [Display(Name = "Role")]
        public string Role { get; set; }

        [DataType(DataType.Text)]
        public string Active { get; set; }
    }

By using EF code first approch, create context class which inherits DBContxt class

public class UserContext : DbContext
    {
        public DbSet<Users> User { get; set; }        
    }

The purpose of data annotations used in model class above are clearly understandable by their names. The important step now is to set new connectionstring by the name of UserContext. So that at the time of creating database context, application can find the proper database connecction information.

Go to web.config file in root directory add this tag under ConnectionStrings tag (the name property is important. Set its value to class name which inherits DBContext class

<add name="UserContext"
     connectionString="Data Source=127.0.0.1\sqlexpress;Initial Catalog=DBName;Integrated Security=SSPI;persist security info=False;"
     providerName="System.Data.SqlClient"/>

Now, go to AccountController.cs file. In the Register ActionResult method which accepts model as a parameter, change code as

[HttpPost]
        public ActionResult Register(Users UserModel)
        {
            if (ModelState.IsValid)
            {
                try
                {
                    Users reg = new Users();
                    reg.UserName = UserModel.UserName;
                    reg.Password = UserModel.Password;
                    reg.FullName = UserModel.FullName;
                    reg.EmailAddress = UserModel.EmailAddress;
                    reg.Role = UserModel.Role;
                    reg.Active = "True";
                    
                    // Attempt to register the user
                    empContext.User.Add(reg);
                    empContext.SaveChanges();
                    FormsAuthentication.SetAuthCookie(UserModel.UserName, false /* createPersistentCookie */);
                    return RedirectToAction("Index", "Home");
                }

            }
            return View(UserModel);
        }

We have completely removed logic of registering user using MemberShip provider. Instead, created new instance of Users model class and initialized it's values. Then added it into context and called SaveChange() methods to persist the new record in underlying database.

As we have created new model for demo purpose, we will also need to make simillar changes in view file. Go to Views -> Account folder and open Rgister.cshtml file. After making necessary changes according to Users model and fields available in Users table, the modified Register.cshtml file looks like

@model MvcApplication1.Models.Users

@{
    ViewBag.Title = "Register";
}

<h2>Create a New Account</h2>
<p>
    Use the form below to create a new account. 
</p>
<p>
    Passwords are required to be a minimum of @Membership.MinRequiredPasswordLength characters in length.
</p>

<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>

@using (Html.BeginForm()) {
    @Html.ValidationSummary(true, "Account creation was unsuccessful. Please correct the errors and try again.")
    <div>
        <fieldset>
            <legend>Account Information</legend>

            <div class="editor-label">
                @Html.LabelFor(m => m.UserName)
            </div>
            <div class="editor-field">
                @Html.TextBoxFor(m => m.UserName)
                @Html.ValidationMessageFor(m => m.UserName)
            </div>

            <div class="editor-label">
                @Html.LabelFor(m => m.FullName)
            </div>
            <div class="editor-field">
                @Html.TextBoxFor(m => m.FullName)
                @Html.ValidationMessageFor(m => m.FullName)
            </div>

            <div class="editor-label">
                @Html.LabelFor(m => m.EmailAddress)
            </div>
            <div class="editor-field">
                @Html.TextBoxFor(m => m.EmailAddress)
                @Html.ValidationMessageFor(m => m.EmailAddress)
            </div>

            <div class="editor-label">
                @Html.LabelFor(m => m.Password)
            </div>
            <div class="editor-field">
                @Html.PasswordFor(m => m.Password)
                @Html.ValidationMessageFor(m => m.Password)
            </div>

           <div class="editor-label">
                @Html.LabelFor(m => m.Role)
            </div>
            <div class="editor-field">
                @Html.TextBoxFor(m => m.Role)
                @Html.ValidationMessageFor(m => m.Role)
            </div>
            <p>
                <input type="submit" value="Register" />
            </p>
        </fieldset>
    </div>
}

This completes the Register functionality with our custom authentication table. Lastly, we will also need to change Logon functionality so that once registered users would get authenticated against our custom table. Again, we will make changes in existing LogOn ActionResult method.

The only change require in default LogOn method is to verify username/password in new created model instead of validating user against Membership provider.

[HttpPost]
        public ActionResult LogOn(LogOnModel model, string returnUrl)
        {
            if (ModelState.IsValid)
            {

                if (empContext.User.Any(r=>r.UserName==model.UserName && r.Password==model.Password))
                {
                    FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
                    if (Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/")
                        && !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\"))
                    {
                        return Redirect(returnUrl);
                    }
                    else
                    {
                        return RedirectToAction("Index", "Home");
                    }
                }
                else
                {
                    ModelState.AddModelError("", "The user name or password provided is incorrect.");
                }
            }

            // If we got this far, something failed, redisplay form
            return View(model);
        }

This completes all the changes. We are now ready to test the custom authentication functionality. Run the page using F5 and go to LogOn link. from there navigate to Register link.

The register page now contains modified fields. We can test the data annotaion validation that we had applied in our model class. keep few fields blank and click on the Register button. The validation message is displayed as below. The validation happens both on server as well as on client side.

After filling all appropriate details click on Register button after successfully creating new user, page will redirect to default page. Now, again go to Logon page to test if the new rgistered user can login to application.

Thats it. You have succefully created custom forms authentication functionality using MVC and EF code first.

Hope this helps some newbi :)