Creating a Slick Login Form in MVC3 C# ASP .NET with Attribute Security, CSS3

modified

Introduction

A common task for many MVC3 C# ASP .NET web applications is the creation of a login form. Along with the common login form comes an array of required functionality, including login validation, authentication, storing of credentials, and of course, security. In traditional C# ASP .NET web applications, protected pages would sit within a particular folder, designated by the web.config as a protected area. However, in MVC3 controllers rule the land and application folders may be virtual, thus requiring a controller-based security technique.

In this tutorial, we’ll walk through creating a slick login form for an example MVC3 C# ASP .NET web application. Our login form will be styled with CSS3, contain rounded edges, a gradient background, and a lens flare highlight effect. Our login form will also contain MVC3 form field validation for the username and password. Finally, we’ll authenticate the user with a custom membership provider and store the user details in cache for quick retrieval.

Login form, created with MVC3 CSS3 C# ASP .NET

Classic ASP .NET Security

In a traditional C# ASP .NET web application, security is often implemented using the web.config authorization XML section. This allows you to define web application folder paths, allowed roles, and denied roles for accessing the specified folder within the web application. If a user is denied access, he is redirected to the forms authentication login page. For example:

1
2
3
4
5
6
7
8
<location path="secure">
<system.web>
<authorization>
<allow roles="admin">
<deny users="*">
</deny></allow></authorization>
</system.web>
</location>

The above web.config block would only allow users with the role of “admin” to access the folder “/secure” in the C# ASP .NET web application.

MVC3 Security is a Little Different

While classic ASP .NET security uses the web.config folder-based approach for protecting a C# ASP .NET web application, MVC3 is quite different. Since an MVC web application relies on routing via virtual folder paths, the web application could not be configured via the web.config, since the folder paths do not actually exist. In fact, even using security based upon the routing itself, is discouraged, due to the complexity of verifying security for all possible combinations of routes within the MVC web application. This can be complicated even further when you consider all possible input combinations to the controllers that could result in different controllers being called.

Rather, the recommended method for securing an MVC web application is to use the [Authorize] attribute on specific controller methods that should be protected. For example, the following code enforces security on the Index() method for the particular controller, requiring a logged in user for access:

1
2
3
4
5
[Authorize]
public ActionResult Index()
{
return View();
}

We Can Do It Better

While the Authorize attribute can be used to protect specific controller actions within an C# MVC3 web application, it’s generally simpler to reverse the security. That is, decorate specific controller actions that are free to be anonymous (such as the login page), and leave all other controller actions secured. In this manner, any new controllers or actions that are created within the MVC3 C# ASP .NET web application will be automatically secured. The developer will need to explicitly decorate the action as [AllowAnonymous] in order for non-logged in users to access the page. This idea comes from Rick Anderson’s post.

For example, the following code explicitly opts-out of security, allowing any user to access the controller action for displaying the login page:

1
2
3
4
5
[AllowAnonymous]
public ActionResult Index()
{
return View();
}

Creating our MVC3 Security Framework

To get started, we’ll begin by implementing our AllowAnonmous attribute for decorating controller actions as anonymous (accessible by non-logged in users). We can create this new attribute by inheriting from the Attribute class, as follows:

1
2
3
4
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class AllowAnonymousAttribute : Attribute
{
}

The above code simply creates a basic AllowAnonymous attribute that can decorate any class or method (ie., controller or controller action). Later on, we’ll check the methods for this attribute to know whether we should enforce security or not.

Implementing the LogonAuthorize Class

Next, we’ll need to create an authorization filter, which gets added to the list of global filters in the MVC3 global filter collection. This tells MVC3 which classes to call to check for security on web requests, in addition to other tasks.

We can implement the LogonAuthorize class, as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public sealed class LogonAuthorize : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
bool skipAuthorization = filterContext.ActionDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true) ||
filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true);

// If the method did not exclusively opt-out of security (via the AllowAnonmousAttribute),
// then check for an authentication ticket.
if (!skipAuthorization)
{
base.OnAuthorization(filterContext);
}
}
}

The above code creates a new LogonAuthorize class that overrides the OnAuthorization method. When our C# MVC3 ASP .NET web application needs to check security for autorization, our global filter will get called. We’ll first check if the calling method contains the AllowAnonymous attribute, indicating that the method is accessible to all users (even non-logged in users). If it does, we’ll skip performing any security check. If the method doesn’t contain this opt-out flag, then we’ll assume security is enforced for this controller and continue with authorization.

Hooking into the MVC3 Global Filters

Finally, we can add the new LogonAuthorize filter to the list of MVC web application filters by using the following code within the Global.asax.cs file:

1
2
3
4
5
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new LogonAuthorize()); // <-- Add this line
filters.Add(new HandleErrorAttribute());
}

Creating our Logon Page Controller

Now that the security framework is ready, we can create our logon page controller to allow the user to enter their username and password for validation. The logon controller will appear, as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public class HomeController : Controller
{
[AllowAnonymous]
public ActionResult Index()
{
// If user has already logged in, redirect to the secure area.
if (Request.IsAuthenticated)
{
return RedirectToAction("Index", "Secure");
}
else
{
return View();
}
}

[HttpPost]
[AllowAnonymous]
public ActionResult Index(LogOnModel model, string returnUrl)
{
// Verify the fields.
if (ModelState.IsValid)
{
// Validate the user login.
if (Membership.ValidateUser(model.Username, model.Password))
{
// Create the authentication ticket.
FormsAuthentication.SetAuthCookie(model.Username, false);

// Redirect to the secure area.
if (Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/") && !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\"))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Secure");
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}

return View(model);
}

public ActionResult LogOff()
{
// Delete the user details from cache.
System.Web.HttpContext.Current.Cache.Remove(User.Identity.Name);

// Delete the authentication ticket and sign out.
FormsAuthentication.SignOut();

return RedirectToAction("Index", "Home");
}
}

The first important note in the above Login controller code is the two Index() methods. The first Index method is for displaying the login page. If the user is already authenticated, we simply redirect to the secure landing page controller. This is a handy shortcut for already-logged-on users, who access the web application at the root Url. If the user is not already logged-in, we display the login form.

The second Index() method above is called upon posting the login form details back to the MVC3 web application. We first validate the form fields (username and password) and display any errors, if needed. Next, we validate the user credentials with the MembershipProvider class. We then create the standard ASP .NET authentication ticket and login the user to the secure area.

A final item to note is the LogOff() method. This method is only available to logged-in users (since it doesn’t contain the [AllowAnonymous] attribute). The LogOff() method deletes any cache, containing the user details, and signs out the logged-in user.

The LogOnModel Class

The login form controller uses a class to hold the username and password details, called LogOnModel. We can define this simple class, as follows:

1
2
3
4
5
6
7
8
9
10
11
public class LogOnModel
{
[Required]
[Display(Name = "Username")]
public string Username { get; set; }

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

Note the class utilizes MVC3 attributes to indicate styling and validation for the login form fields.

MVC3 Forms Authentication in the Web.Config

We’ll take a quick break here to add some configuration to the web.config file. We’ll need to enable MVC3 forms authentication, as well as define our custom membership provider class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<system.web>
...

<authentication mode="Forms">
<forms loginurl="~/" slidingexpiration="true" timeout="20">
</forms></authentication>

<membership defaultprovider="myMembershipProvider">
<providers>
<clear>
<add name="myMembershipProvider" type="LoginFormExample.Providers.MyMembershipProvider">
</add></clear></providers>
</membership>

...
</system.web>

In the above example, we’ve simply defined a forms authentication block with a 20 minute sliding timeout. If the user remains inactive for longer than 20 minutes, he’ll be automatically logged out of the MVC3 C# ASP .NET web application. We’ve also defined our custom membership provider class for performing authentication.

Creating the MVC3 Custom MembershipProvider

In the login form controller, we make a call to Membership.ValidateUser() to perform the authentication. In order to make this call, we need to define a custom membership provider class, where our actual code for checking the username and password will reside.

Creating a custom membership provider in an MVC3 web application is actually no different than creating one in a classic C# ASP .NET web application. We’ll simply inherit from MembershipProvider and implement the required methods, as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class MyMembershipProvider : MembershipProvider
{
/// <summary>
/// Authenticates the user.
/// </summary>
///<param name="username" />Username, for testing use: testuser
///<param name="password" />Password, for testing use: password
/// <returns>bool</returns>
public override bool ValidateUser(string username, string password)
{
if (username == "testuser" && password == "password")
{
// Simulate a user id.
int userId = new Random().Next(1, 100);

// Add the user details to cache, for quick retrieval later.
HttpContext.Current.Cache.Add(username, new MyMembershipUser(userId, username), null,
Cache.NoAbsoluteExpiration, FormsAuthentication.Timeout,
CacheItemPriority.Default, null);

return true;
}
else
{
return false;
}
}

/// <summary>
/// Retrieves the user details for a particular user.
/// </summary>
///<param name="username" />User to retrieve user details for
///<param name="userIsOnline" />
/// <returns>MembershipUser</returns>
public override MembershipUser GetUser(string username, bool userIsOnline)
{
// Retrieve the user details from cache.
MyMembershipUser membershipUser = (MyMembershipUser)HttpContext.Current.Cache.Get(username);

return membershipUser;
}
}

In the above code, the most important method is the ValidateUser() method. This is called automatically by the MVC3 .NET web application framework to validate our user’s credentials. We add our custom code to check the username and password. You would probably be checking against a database, stored procedure, or web service to perform your actual authentication.

Remembering the User Id

Once the username and password is verified, we add the Username and User ID to the cache for quick retrieval later on in the application. We’ll provide the user details in the GetUser() method. This is accessible from anywhere in the MVC3 application. For example, to retrieve the user id, the following call can be used:

1
Membership.GetUser().ProviderUserKey

Since the user details are stored within the cache, it saves us an extra step of hitting the database for each call for the user’s details. Once the user logs out, we’ll remove the cache entry, as well as delete the forms authentication ticket.

Synchronizing the User Cache with Forms Authentication Timeout

It’s important to note the sliding timeout value set on the Cache.Add() call. We use the same timeout value as the FormsAuthentication.Timeout value (in our example, 20 minutes). This allows us to expire the cache at the same time as the MVC3 forms authentication ticket. However, this will only work if every page request is loading the user details. Otherwise, the cache could expire sooner than the forms authentication ticket. In our example, the user’s name and id are displayed in the top menu of the secured area, which results in a call to the cached data in GetUser() for each page view. This optimizes our web application for speed.

Since our MVC3 web application now relies on cache information for retrieving the user id (in addition to the regular forms authentication ticket), we’ll need to add a check in Global.asax.cs to make sure the cache is available upon request. You can modify the Global.asax.cs to add the following code:

1
2
3
4
5
6
7
8
9
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
// If the user is logged-in, make sure his cache details are still
// available, otherwise redirect to login page.
if (Request.IsAuthenticated && Membership.GetUser() == null)
{
FormsAuthentication.SignOut();
}
}

Creating a Simple Custom MembershipUser Class

In our custom MembershipProvider class, when we return the user details via GetUser(), we’re actually returning a custom MembershipUser object, named MyMembershipUser. This is a shortcut class that wraps the base MembershipUser class with a simple username/user id combination. We can define it, as follows:

1
2
3
4
5
6
7
8
public class MyMembershipUser : MembershipUser
{
public MyMembershipUser(long userId, string userName) : base(Membership.Provider.Name, userName,
userId, null, null, null, true, false, DateTime.Now,
DateTime.Now, DateTime.Now, DateTime.Now, DateTime.Now)
{
}
}

The above code allows us to create a new MembershipUser, with default values, by simply passing in a username and user id.

Our MVC3 C# ASP .NET web application security is now complete. We can now create the login form view and utilize some fancy CSS3 to spice up the form.

Creating the Shared MVC3 Layout View

We’ll start, as with most MVC3 web applications, by defining our master page, also called the Layout. We’ll define our basic layout with a CDN (content distribution network) link to the Google-hosted jQuery API, as well as a link to our default CSS stylesheet.

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html>
<head>
<title>@ViewBag.Title | MVC3 Login Form Example</title>
<link href="@Url.Content("~/Content/css/style.css")" rel="Stylesheet" type="text/css" />
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.0/jquery.min.js" type="text/javascript"></script>
</head>

<body>
@Html.Partial("~/Views/Controls/UserMenu.cshtml")
@RenderBody()
</body>
</html>

In the above code, we also render a partial view, which is our user control for the top menu (displayable only to logged-in users). The UserMenu.cshtml is defined as follows:

1
2
3
4
5
6
7
8
@if (Request.IsAuthenticated)
{
<p align="right">
@User.Identity.Name (@Membership.GetUser().ProviderUserKey)
|
@Html.ActionLink("Logout", "LogOff", "Home")
</p>
}

Note the call to Request.IsAuthenticated, which allows us to only display the menu-bar control to authenticated and logged-in users.

Detecting Internet Explorer in HTML

Before we create our MVC3 CSS3 enabled login form, we’ll need to detect Internet Explorer from other web browsers. While Chrome and Firefox offer support for most CSS3 features, IE9 may not support all commands. We can therefore, detect IE from other web browsers within the HTML with the following code:

1
2
3
4
5
6
7
<!--[if !IE]><!-->
<link href="@Url.Content("~/Content/css/style-login.css")" rel="Stylesheet" type="text/css" />
<!--<![endif]-->
<!--[if IE]>
<meta http-equiv="X-UA-Compatible" content="IE=9" />
<link href="@Url.Content("~/Content/css/style-login-ie.css")" rel="Stylesheet" type="text/css" />
<![endif]-->

The above code first checks if the web browser is not Internet Explorer, and if so, it includes the regular style-login.css file. If the web browser is IE, it instead uses the style-login-ie.css. All other styles (included in style.css) are shared by all web browsers for our web application. In this example, CSS3 is only used on the login form.

Designing Our MVC3 CSS3 Login Form

Our MVC3 C# ASP .NET login form view will be strongly-typed to our LogOnModel class. Since we’ll be using form validation, we’ll include two CDN links to jQuery Validate and jQuery Validate Unobtrusive. We’ll also link to our login form css file, depending on the web browser, and display our form fields. The code appears, as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
@model LoginFormExample.Models.LogOnModel

@{
ViewBag.Title = "Login";
}

<script src="http://ajax.aspnetcdn.com/ajax/jquery.validate/1.9/jquery.validate.min.js" type="text/javascript"></script>
<script src="http://ajax.aspnetcdn.com/ajax/mvc/3.0/jquery.validate.unobtrusive.min.js" type="text/javascript"></script>

<!--[if !IE]><!-->
<link href="@Url.Content("~/Content/css/style-login.css")" rel="Stylesheet" type="text/css" />
<!--<![endif]-->
<!--[if IE]>
<meta http-equiv="X-UA-Compatible" content="IE=9" />
<link href="@Url.Content("~/Content/css/style-login-ie.css")" rel="Stylesheet" type="text/css" />
<![endif]-->

<div class="login-form-container">
<div class="header">
<h2>Login Form Example</h2>
</div>

<center>
@Html.ValidationSummary(true, "Login was unsuccessful. Please correct the errors and try again.")
</center>

@using (Html.BeginForm())
{
<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)
<div class="validation-line">
@Html.ValidationMessageFor(m => m.Username)
</div>
</div>

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

<p>
<input type="submit" value="Log On" />
</p>
</fieldset>
</div>
}
</div>

<script>
document.body.style.backgroundColor = '#c0c0c0';
</script>

Note in the above code, MVC3 will use the model’s attributes to determine validation on required fields. The above form fields will post to our HomeController’s Index() method for validation of the form fields and user credentials.

Mix in a Little CSS3 For the Slick Effects

We can add some style to the login form with the help of CSS3. Specifically, we’ll add rounded corners to the login form frame, a background gradient to the login form panel, and a light shadow behind the frame. We’ll also add a CSS3 transition effect to the input fields to give our MVC3 login form a slick look. The following styles are defined for the login form, and tested in Google Chrome and Firefox:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
.login-form-container form 
{
width: 500px;
padding: 20px;
border: 1px solid #c0c0c0;

/*** Rounded Corners ***/
-moz-border-radius: 20px;
-webkit-border-radius: 20px;

/*** Background Gradient - 2 declarations one for Firefox and one for Webkit ***/
background: -moz-linear-gradient(19% 75% 90deg, #FFFFFF, #d0d0d0);
background:-webkit-gradient(linear, 0% 0%, 0% 100%, from(#d0d0d0), to(#FFFFFF));

/*** Shadow behind the box ***/
-moz-box-shadow:0px -1px 70px #f0f0f0;
-webkit-box-shadow:0px -1px 70px #f0f0f0;
}

.login-form-container input
{
width: 230px;
padding: 6px;
margin-bottom: 10px;
border-top: 1px solid #d0d0d0;
border-left: 0px;
border-right: 0px;
border-bottom: 0px;

/*** Transition Selectors - What properties to animate and how long ***/
-webkit-transition-property: -webkit-box-shadow, background;
-webkit-transition-duration: 0.25s;

/*** Adding a small shadow ***/
-moz-box-shadow: 0px 0px 2px #000;
-webkit-box-shadow: 0px 0px 2px #000;
}

.login-form-container input:hover
{
-webkit-box-shadow: 0px 0px 4px #000;
background: #efefff;
}

CSS3 and Internet Explorer 9

IE9 is slightly different with its support for CSS3. As such, we’ll need to change the login form styles to be compatible. In the above HTML code for the MVC3 view for the login form, we’ve added code to detect Internet Explorer in HTML. We can then provide the following styles, specific to IE:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.login-form-container form 
{
width: 500px;
padding: 20px;
border: 1px solid #c0c0c0;
filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr='#d0d0d0', endColorstr='#ffffff'); /* IE6 & IE7 */
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr='#d0d0d0', endColorstr='#ffffff')"; /* IE8 */
}

.login-form-container input
{
width: 230px;
padding: 6px;
margin-bottom: 10px;
}

After Logging In

We’ve finished our login form, complete with CSS3 gradients, rounded edges, and fancy form field animation. The final task is to create a simple secured page for our logged in user to access. We can define the SecureController, as follows:

1
2
3
4
5
6
7
public class SecureController : Controller
{
public ActionResult Index()
{
return View();
}
}

Note the controller’s method does not contain an attribute. Since it doesn’t contain the [AllowAnonymous] attribute, only logged-in users may access this method.

Our MVC3 Secure controller view, appears as follows:

1
2
3
4
5
6
7
@model LoginFormExample.Models.SecureModel

@{
ViewBag.Title = "Logged In!";
}

<h2>Welcome, Logged In User</h2>

You can continue from here to create additional controllers, views, and pages accessible only by logged-in users.

See It In Action

Download @ GitHub

You can download the project source code on GitHub by visiting the project home page.

About the Author

This article was written by Kory Becker, software developer and architect, skilled in a range of technologies, including web application development, machine learning, artificial intelligence, and data science.

Share