OpenID Authentication with ASP.NET MVC3 , DotNetOpenAuth and OpenID-Selector
I will introduce here how to extract and Integrate OpenID with MVC3 and Microsoft Membership Provider .
By using DotNetOpenAuth and OpenID-Selector
Note: I will Introduce another article to Integrate OpenID with MVC3 and Custom Membership Provider.
Let us start :
1- Open Visual Studio 2010 go to File > New > Project > Web > ASP.NET MVC 3 Application:
then Choose Internet Application be sure to have Razor as your View engine and Click Ok:
2- Download Assets folder , it contains DotNetOpenAuth dll and OpenID-Selector files that we will use ,
Feel free if you want to go to these projects and discover them in more details.
Extract it to the folder you want
a - Add the DotNetOpenAuth.dll to references in your site.
b- Delete all files/folders in Site Content folder.
c- Copy Assets Content files/folders to the site Content .
d- Copy the Assets Script files to the site Script.
.
Your project will look like this :
3- Go to Views > Shared > _Layout.cshtml and replace the <head> with this new head , we just added the new styles and scripts:
<head> <title>@ViewBag.Title</title> <link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" /> <script src="@Url.Content("~/Scripts/jquery-1.4.4.min.js")" type="text/javascript"></script> <link href="@Url.Content("~/Content/openid-shadow.css")" rel="stylesheet" type="text/css" /> <link href="@Url.Content("~/Content/openid.css")" rel="stylesheet" type="text/css" /> <script src="@Url.Content("~/Scripts/openid-en.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/openid-jquery.js")" type="text/javascript"></script> <script type="text/javascript"> $(document).ready(function () { openid.init('openid_identifier'); }); </script> </head>
4- Go to Models > AccountModels.cs , navigate to public class LogOnModel
and Add OpenID attribute that we will use it to hold the returned OpenID from OpenID-Selector
your class will look like this:
public class LogOnModel { [Display(Name = "OpenID")] public string OpenID { get; set; } [Required] [Display(Name = "User name")] public string UserName { get; set; } [Required] [DataType(DataType.Password)] [Display(Name = "Password")] public string Password { get; set; } [Display(Name = "Remember me?")] public bool RememberMe { get; set; } }
navigate to public class RegisterModel and Add OpenID attribute
public class RegisterModel { [Display(Name = "OpenID")] public string OpenID { get; set; } [Required] [Display(Name = "User name")] public string UserName { get; set; } [Required] [DataType(DataType.EmailAddress)] [Display(Name = "Email address")] public string Email { get; set; } [Required] [ValidatePasswordLength] [DataType(DataType.Password)] [Display(Name = "Password")] public string Password { get; set; } [DataType(DataType.Password)] [Display(Name = "Confirm password")] [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] public string ConfirmPassword { get; set; } }
Then go to Services section in AccountModels.cs
and Modify the CreateUser and Add GetUser to get the user by OpenID , your Interface
will looks like this:
public interface IMembershipService { int MinPasswordLength { get; } bool ValidateUser(string userName, string password); MembershipCreateStatus CreateUser(string userName,string password, string email,string OpenID); bool ChangePassword(string userName, string oldPassword, string newPassword); MembershipUser GetUser(string OpenID); }
Add these using to AccountModels.cs
using System.Security.Cryptography; using System.Text;Then Add This Function to the AccountModels.cs , this function will be used to convert the OpenID to GUID
Note: feel free to use better hashing to your system , MD5 had some collision issues.
public Guid StringToGUID(string value) { // Create a new instance of the MD5CryptoServiceProvider object. MD5 md5Hasher = MD5.Create(); // Convert the input string to a byte array and compute the hash. byte[] data = md5Hasher.ComputeHash(Encoding.Default.GetBytes(value)); return new Guid(data); }
Also Modify The CreateUser Function to look like this:
public MembershipCreateStatus CreateUser(string userName, string password, string email , string OpenID) { if (String.IsNullOrEmpty(userName)) throw new ArgumentException("Value cannot be null or empty.", "userName"); if (String.IsNullOrEmpty(password)) throw new ArgumentException("Value cannot be null or empty.", "password"); if (String.IsNullOrEmpty(email)) throw new ArgumentException("Value cannot be null or empty.", "email"); MembershipCreateStatus status; _provider.CreateUser(userName, password, email, null, null, true, StringToGUID(OpenID), out status); return status; }
Here we are using the MemberShip ProviderUserKey to store the OpenID and the trick here that we convert the OpenID string to GUID to be used by CreateUser and GetUser methods.
Now let us add this function to AccountModels.cs that will get the user by OpenID:
public MembershipUser GetUser(string OpenID) { return _provider.GetUser(StringToGUID(OpenID), true); }
5- go to Views > Account > LogOn.cshtml
replace all the markup with this one ,we are integrating OpenID-Selector to LogOn View:
@model OpenIDMVC3.Models.LogOnModel @{ ViewBag.Title = "Log On"; } <h2> Log On</h2> <p> Please enter your username and password. @Html.ActionLink("Register", "Register") if you don't have an account. </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> <form action= "Authenticate?ReturnUrl=@HttpUtility.UrlEncode(Request.QueryString["ReturnUrl"])" method="post" id="openid_form"> <input type="hidden" name="action" value="verify" /> <div> <fieldset> <legend>Login using OpenID</legend> <div class="openid_choice"> <p> Please click your account provider:</p> <div id="openid_btns"> </div> </div> <div id="openid_input_area"> @Html.TextBox("openid_identifier") <input type="submit" value="Log On" /> </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> <div> @if (Model != null) { if (String.IsNullOrEmpty(Model.UserName)) { <div class="editor-label"> @Html.LabelFor(model => model.OpenID) </div> <div class="editor-field"> @Html.DisplayFor(model => model.OpenID) </div> <p class="button"> @Html.ActionLink("New User ,Register", "Register", new { OpenID = Model.OpenID }) </p> } else { //user exist <p class="buttonGreen"> <a href="@Url.Action("Index", "Home")">Welcome , @Model.UserName, Continue..." </a> </p> } } </div> </fieldset> </div> </form> @Html.ValidationSummary(true, "Login was unsuccessful. Please correct the errors and try again.") @using (Html.BeginForm()) { <div> <fieldset> <legend>Or Login Normally</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.Password) </div> <div class="editor-field"> @Html.PasswordFor(m => m.Password) @Html.ValidationMessageFor(m => m.Password) </div> <div class="editor-label"> @Html.CheckBoxFor(m => m.RememberMe) @Html.LabelFor(m => m.RememberMe) </div> <p> <input type="submit" value="Log On" /> </p> </fieldset> </div> }
6- Now let us run the project , then click the [Log On] link , you will get like this page:
7- Go to Controllers > AccountController.cs and Add these using:
using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId; using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; using DotNetOpenAuth.OpenId.RelyingParty; using DotNetOpenAuth.OpenId.Extensions.AttributeExchange;
Then Add this Attribute to AccountController.cs:
private static OpenIdRelyingParty openid = new OpenIdRelyingParty();
Then Add this Function to AccountController.cs:
[ValidateInput(false)] public ActionResult Authenticate(string returnUrl) { var response = openid.GetResponse(); if (response == null) { //Let us submit the request to OpenID provider Identifier id; if (Identifier.TryParse(Request.Form["openid_identifier"], out id)) { try { var request = openid.CreateRequest( Request.Form["openid_identifier"]); return request.RedirectingResponse.AsActionResult(); } catch (ProtocolException ex) { ViewBag.Message = ex.Message; return View("LogOn"); } } ViewBag.Message = "Invalid identifier"; return View("LogOn"); } //Let us check the response switch (response.Status) { case AuthenticationStatus.Authenticated: LogOnModel lm = new LogOnModel(); lm.OpenID = response.ClaimedIdentifier; // check if user exist MembershipUser user = MembershipService.GetUser(lm.OpenID); if (user != null) { lm.UserName = user.UserName; FormsService.SignIn(user.UserName, false); } return View("LogOn", lm); case AuthenticationStatus.Canceled: ViewBag.Message = "Canceled at provider"; return View("LogOn"); case AuthenticationStatus.Failed: ViewBag.Message = response.Exception.Message; return View("LogOn"); } return new EmptyResult(); }
8 - Now run the project click [Log On] link and click a provider like Google
it may ask you to sign in or ask you to allow access to your information
you will get a page like this :
As you can see it displays your OpenID and a button that indicate that this is a new user not registered yet,
before hitting [New User ,Register] button we need to modify the Register view and controller to access OpenID information.
9- Go to controllers > AccountController.cs replace the [ActionResult Register ()] by this :
public ActionResult Register(string OpenID) { ViewBag.PasswordLength = MembershipService.MinPasswordLength; ViewBag.OpenID = OpenID; return View(); }
And Modify the [ActionResult Register(RegisterModel model)] to use OpenID when
creating users:
[HttpPost] public ActionResult Register(RegisterModel model) { if (ModelState.IsValid) { // Attempt to register the user MembershipCreateStatus createStatus = MembershipService.CreateUser(model.UserName, model.Password, model.Email,model.OpenID); if (createStatus == MembershipCreateStatus.Success) { FormsService.SignIn(model.UserName, false); return RedirectToAction("Index", "Home"); } else { ModelState.AddModelError("", AccountValidation.ErrorCodeToString(createStatus)); } } // If we got this far, something failed, redisplay form ViewBag.PasswordLength = MembershipService.MinPasswordLength; return View(model); }
10- Go to Views > Account > Register.cshtml , replace the markup by this :
@model OpenIDMVC3.Models.RegisterModel @{ 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 @ViewBag.PasswordLength 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> @if (ViewData["OpenID"] != null) { <div class="editor-label"> @Html.Label("OpenID") </div> <div class="editor-label"> @Html.Label((string)ViewBag.OpenID) </div> } <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.Email) </div> <div class="editor-field"> @Html.TextBoxFor(m => m.Email) @Html.ValidationMessageFor(m => m.Email) </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.ConfirmPassword) </div> <div class="editor-field"> @Html.PasswordFor(m => m.ConfirmPassword) @Html.ValidationMessageFor(m => m.ConfirmPassword) </div> <p> <input type="submit" value="Register" /> </p> </fieldset> </div> }
11- Go to step 8 and let us hit [New User ,Register] button , you will get this :
12- Register any account you want you will get like this page :
13- Click [Log Off] and login again using the same OpenID , you will get like this page:
As you can see the welcome green button detect that this user is registered .
14- Click the green button you will get a page like this :
Congratulation ! , now you had integrated OpenID to your project.