Create Login Page

For this example, we will be using DotNetOpenAuth for user authentication and the openid-selector control to provide the sign-in interface.  OpenId defines a standard for user authentication via a trusted third-party.

First, we will modify the User class to implement System.Security.Principal.IIdentity. This will allow us to associate the authenticated user with the current Request context for later use by the application.

Now that we have a representation in the model of the User, we need to create a view and corresponding controller.  The controller will contain all of the necessary logic to handle the OpenId authentication handshake.  Upon receiving a response from the OpenId provider selected by the user, the system will use the unique identifier from the provider to determine if the user exists in the system.  If the user does not exist, a new record is created within the database and an Asp.Net authentication cookie is created for the session.

The sign-in view consists of a small amount of markup as well as the OpenId selector control.  This control provides a means of displaying a friendly interface for authentication as well as submission of the user's selection.  Although not strictly necessary, this control makes it fairly easy to get started quickly:

OpenId selector

Having completed the interface, it's time to install some of the plumbing.  The first task is to modify the web.config file to indicate to the application where to send unauthenticated users.  This is simply a matter of modifying the following lines of code:

<authentication mode="Forms">

   <forms loginUrl="~/User" timeout="2880" />

</authentication>

This indicates to Asp.Net that unauthenticated users - upon reaching a page that requires authentication - should be redirected to ~/Users.

The Global.asax.cs file must then be modified so that upon each request, if a valid authentication cookie is found, the system should attempt to load the associated User instance and bind it to the current request context.


  1. Modify the User class to implement System.Security.Principal.IIdentity.
  2. Insert the following code snippet into User class:
    		[Required]
    		public string OpenId { get; set; }
    
    		[NotMapped]
    		public string AuthenticationType
    		{
    			get { return "TodoApp"; }
    		}
    
    		[NotMapped]
    		public bool IsAuthenticated
    		{
    			get { return true; }
    		}
    				
  3. Add a controller called UserController.cs to the Controllers folder.
  4. Replace the contents of UserController.cs with the following code snippet:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Mvc;
    using DotNetOpenAuth.OpenId.RelyingParty;
    using System.Web.Security;
    using DotNetOpenAuth.OpenId;
    using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration;
    using DotNetOpenAuth.OpenId.Extensions.AttributeExchange;
    using TodoApp.Models;
    using DotNetOpenAuth.Messaging;
    using System.ComponentModel.DataAnnotations;
    using ExoModel;
    
    namespace TodoApp.Controllers
    {
    	public class UserController : Controller
    	{
    		private static OpenIdRelyingParty openid = new OpenIdRelyingParty();
    
    		public ActionResult Index()
    		{
    			if (HttpContext.User.Identity.IsAuthenticated)
    				return RedirectToAction("Index", "Home");
    
    			return View();
    		}
    
    		[HttpGet]
    		public ActionResult LogOff()
    		{
    			Session.Abandon();
    			FormsAuthentication.SignOut();
    
    			return RedirectToAction("Index", "User");
    		}
    
    		[ValidateInput(false)]
    		public ActionResult Authenticate()
    		{
    			string provider = Request.Form["openid_identifier"];
    
    			var response = openid.GetResponse();
    			if (response == null)
    			{
    				Identifier id = null;
    				if (Identifier.TryParse(provider, out id))
    				{
    					// Redirect user to provider for authentication
    					try
    					{
    						var request = openid.CreateRequest(id);
    						var fields = new ClaimsRequest()
    						{
    							Email = DemandLevel.Require,
    							FullName = DemandLevel.Require,
    							Nickname = DemandLevel.Require
    						};
    						request.AddExtension(fields);
    
    						var fetch = new FetchRequest();
    						fetch.Attributes.AddRequired(WellKnownAttributes.Name.First);
    						fetch.Attributes.AddRequired(WellKnownAttributes.Name.Last);
    						fetch.Attributes.AddRequired(WellKnownAttributes.Name.Alias);
    						fetch.Attributes.AddRequired(WellKnownAttributes.Name.FullName);
    						request.AddExtension(fetch);
    
    						return request.RedirectingResponse.AsActionResult();
    					}
    					catch (ProtocolException e)
    					{
    						ViewData["Message"] = e.Message;
    					}
    				}
    			}
    			else
    			{
    				// Handle response from provider
    				switch (response.Status)
    				{
    					case AuthenticationStatus.Authenticated:
    						var claimedIdentifier = response.ClaimedIdentifier.ToString();
    						var user = TodoContext.Current.Users.Where(u => u.OpenId == claimedIdentifier).FirstOrDefault();
    
    						if (user == null)
    						{
    							var claim = response.GetExtension<ClaimsResponse>();
    							var fetch = response.GetExtension<FetchResponse>();
    
    							// Create the new user
    							user = ModelContext.Create<User>();
    							user.Name = fetch.Attributes[WellKnownAttributes.Name.First].Values[0];
    							user.OpenId = claimedIdentifier;
    
    							// Add a default list to the new user
    							var defaultList = ModelContext.Create<List>();
    							defaultList.Name = "My First List";
    							user.Lists.Add(defaultList);
    
    							// Add a default item to the default list 
    							var defaultItem = ModelContext.Create<ListItem>();
    							defaultItem.DateCreated = DateTime.Now;
    							defaultItem.DueDate = DateTime.Now;
    							defaultItem.Description = "Be Productive!";
    							defaultList.Items.Add(defaultItem);
    
    							// Save the new user
    							TodoContext.Current.SaveChanges();
    						}
    
    						FormsAuthenticationTicket authTicket = new
    							FormsAuthenticationTicket(1, //version
    							response.ClaimedIdentifier, // user name
    							DateTime.Now,             //creation
    							DateTime.Now.AddMinutes(30), //Expiration
    							false, //Persistent
    							user.OpenId); 					
    									
    						string encTicket = FormsAuthentication.Encrypt(authTicket);
    
    						this.Response.Cookies.Add(new HttpCookie(FormsAuthentication.FormsCookieName, encTicket));
    
    						return RedirectToAction("Index", "Home");
    					case AuthenticationStatus.Canceled:
    						break;
    					case AuthenticationStatus.Failed:
    						break;
    				}
    			}
    
    			return RedirectToAction("Index", "Home");
    		}
    	}
    }
    				
  5. Create a User folder under Views.
  6. Add a view called Index.cshtml to the User folder.
  7. Replace the contents of Index.cshtml with the following code snippet:
    @{
    	Layout = string.Empty;
    	ViewBag.Title = "Index";
    }
    
    <!DOCTYPE html>
    
    <html>
    <head>
    	<link type="text/css" rel="stylesheet" href="@Url.Content("~/scripts/openid-selector/css/openid.css")" />
    	<link rel="stylesheet" href="@Url.Content("~/content/board.css")">
    	<script src="@Url.Content("~/scripts/modernizr-1.7.min.js")"></script>
    	<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js"></script> 
    	<script type="text/javascript" src="@Url.Content("~/scripts/openid-selector/js/openid-jquery.js")"></script>
    	<script type="text/javascript" src="@Url.Content("~/scripts/openid-selector/js/openid-en.js")"></script>
    	<script type="text/javascript">
    		$(document).ready(function () {
    			openid.img_path = "scripts/openid-selector/images/";
    			openid.init('openid_identifier');
    		});
    	</script>
    
    	<style type="text/css">
    		body
    		{
    			font: 13px/1.231 sans-serif;
    		}
    		
    		header 
    		{
    			background: #257;
    			padding: 10px;
    			border-radius: 5px;
    		}
    		
    		header h2
    		{
    			color: #fff;
    		}
    	</style>
    </head>
    
    <body>
    <header>
    	<h2>To Do Application!</h2>
    </header>
    
    <p>
    @using (Html.BeginForm("Authenticate", "User", FormMethod.Post, new { id = "openid_form" }))
    {
    	<input type="hidden" name="action" value="verify" />
    	<fieldset>
    		<legend>Sign-in or Create New Account</legend>
    		<div id="openid_choice">
    			<p>Please click your account provider:</p>
    			<div id="openid_btns"></div>
    		</div>
    		<div id="openid_input_area">
    			<input id="openid_identifier" name="openid_identifier" type="text" value="http://" />
    			<input id="openid_submit" type="submit" value="Sign-In"/>
    		</div>
    		<noscript>
    			<p>OpenID is service that allows you to log-on to many different websites using a single indentity.
    			Find out <a href="http://openid.net/what/">more about OpenID</a> and <a href="http://openid.net/get/">how to get an OpenID enabled account</a>.</p>
    		</noscript>
    	</fieldset>
    }
    </p>
    </body>
    </html>
    				
  8. Copy the following Open ID scripts and dependencies into scripts folder: openid-selector.zip
  9. Modify the root Web.config to set the loginUrl to ~/User:

    <authentication mode="Forms">

       <forms loginUrl="~/User" timeout="2880" />

    </authentication>


  10. Add the following method snippet to the MvcApplication class in the Global.asax.cs
    		void Application_PostAuthenticateRequest(object sender, EventArgs e)
    		{
    			HttpCookie authCookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
    			if (authCookie != null)
    			{
    				string encTicket = authCookie.Value;
    				if (!String.IsNullOrEmpty(encTicket))
    				{
    					FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(encTicket);
    
    					var user = TodoContext.Current.Users.Where(p => p.OpenId == ticket.UserData).First();
    
    					GenericPrincipal prin = new GenericPrincipal(user, null);
    
    					HttpContext.Current.User = prin;
    				}
    			}
    		}