DotnetOpenAuth: Usage and Implementation in MVC 5

image

I would like to demonstrate an implementation using DotNetOpenAuth to allow users to log into an application using Google, Yahoo or any other OpenId. Our target is to perform the following actions

  • Allow user to sign in using Google, Yahoo, other Open Id or regular ASP.NET membership account
  • On initial sign-in capture information like First name, Last name, Email from external source
  • If user is logging for the first time then save the membership information
  • On subsequent logins retrieve the user’s record from ASP.NET membership tables.

For more information about DotnetOpenAuth you can refer http://dotnetopenauth.net/. You can download the source code of DotnetOpenAuth from Sourceforge.net.

Solution: For providing a better way to demonstrate how DotNetOpenAuth can be configured and implemented, I thought of creating a blank ASP.NET MVC application in Visual Studio 2013. Initially this application have no controllers, no views, no content except the required folders. From here I will go step by step of development. The entire project source code is referred at the end of this post and you can download the whole project.

Step 1: Initially once my project got created,  I have few folders (Views, Model, Controller) and a web.config file in my solution. I need some libraries to get installed and referred to my solution for further processing. So I went upon to install DotnetOpenAuth, Bootstrap and JQuery files using NUGET package manager.

Installing DotnetOpenAuth using Nuget Package: install-package DotNetOpenAuth

 

PM>install-package DotNetOpenAuth
Attempting to resolve dependency 'DotNetOpenAuth.OpenId.RelyingParty.UI (= 4.3.4.13329)'.
Attempting to resolve dependency 'DotNetOpenAuth.OpenId.Core.UI (= 4.3.4.13329)'.
Attempting to resolve dependency 'DotNetOpenAuth.Core.UI (= 4.3.4.13329)'.
Attempting to resolve dependency 'DotNetOpenAuth.Core (= 4.3.4.13329)'.
Attempting to resolve dependency 'Microsoft.Net.Http'.
Attempting to resolve dependency 'Microsoft.Bcl (≥ 1.1.9)'.
Attempting to resolve dependency 'Microsoft.Bcl.Build (≥ 1.0.14)'.
Attempting to resolve dependency 'DotNetOpenAuth.OpenId.RelyingParty (= 4.3.4.13329)'.
Attempting to resolve dependency 'DotNetOpenAuth.OpenId.Core (= 4.3.4.13329)'.
Attempting to resolve dependency 'DotNetOpenAuth.OpenId.Provider.UI (= 4.3.4.13329)'.
Attempting to resolve dependency 'DotNetOpenAuth.OpenId.Provider (= 4.3.4.13329)'.
Attempting to resolve dependency 'DotNetOpenAuth.OAuth.Consumer (= 4.3.4.13329)'.
Attempting to resolve dependency 'DotNetOpenAuth.OAuth.Core (= 4.3.4.13329)'.
Attempting to resolve dependency 'DotNetOpenAuth.OAuth.ServiceProvider (= 4.3.4.13329)'.
Attempting to resolve dependency 'DotNetOpenAuth.OAuth.Common (= 4.3.4.13329)'.
Attempting to resolve dependency 'DotNetOpenAuth.OpenIdInfoCard.UI (= 4.3.4.13329)'.
Attempting to resolve dependency 'DotNetOpenAuth.InfoCard.UI (= 4.3.4.13329)'.
Attempting to resolve dependency 'DotNetOpenAuth.InfoCard (= 4.3.4.13329)'.
Attempting to resolve dependency 'DotNetOpenAuth.OpenIdOAuth (= 4.3.4.13329)'.
Attempting to resolve dependency 'DotNetOpenAuth.OAuth2.Client.UI (= 4.3.4.13329)'.
Attempting to resolve dependency 'DotNetOpenAuth.OAuth2.Client (= 4.3.4.13329)'.
Attempting to resolve dependency 'DotNetOpenAuth.OAuth2.ClientAuthorization (= 4.3.4.13329)'.
Attempting to resolve dependency 'DotNetOpenAuth.OAuth2.Core (= 4.3.4.13329)'.
Attempting to resolve dependency 'DotNetOpenAuth.OAuth2.AuthorizationServer (= 4.3.4.13329)'.
Attempting to resolve dependency 'DotNetOpenAuth.OAuth2.ResourceServer (= 4.3.4.13329)'.

Installing Bootstrap using Nuget Package: install-package Bootstrap

PM>install-package bootstrap
Attempting to resolve dependency 'jQuery (≥ 1.9.0)'.
Installing 'jQuery 1.9.0'.
Successfully installed 'jQuery 1.9.0'.
Installing 'bootstrap 3.3.1'.

Updating JQuery using Nuget Package: update-package JQuery

 
PM>update-package jquery
Updating 'jQuery' from version '1.9.0' to '2.1.1' in project 'DotnetOpenAuth.Demo'.
Removing 'jQuery 1.9.0' from DotnetOpenAuth.Demo.
Successfully removed 'jQuery 1.9.0' from DotnetOpenAuth.Demo.
Adding 'jQuery 2.1.1' to DotnetOpenAuth.Demo.
Installing 'jQuery 2.1.1'.

Install JQuery Validation using Nuget Package: install-package jQuery.Validation

PM>install-package jQuery.Validation
Attempting to resolve dependency 'jQuery (≥ 1.4.4)'.
Installing 'jQuery.Validation 1.13.1'.
Successfully installed 'jQuery.Validation 1.13.1'.
Adding 'jQuery.Validation 1.13.1' to DotnetOpenAuth.Demo.
Successfully added 'jQuery.Validation 1.13.1' to DotnetOpenAuth.Demo.

Install JQuery Unobtrusive Validation using Nuget Package: install-package Microsoft.jQuery.Unobtrusive.Validation

PM>Install-Package Microsoft.jQuery.Unobtrusive.Validation
Attempting to resolve dependency 'jQuery (≥ 1.8)'.
Attempting to resolve dependency 'jQuery.Validation (≥ 1.8)'.
Installing 'Microsoft.jQuery.Unobtrusive.Validation 3.2.2'.

So far so good. Let’s go ahead and modify the project and add necessary files required for further work to get completed.

Step 2: I have downloaded JavaScript OpenID Selector which is designed for users to select their OpenID account (Facebook, Twitter, LinkedIn, Goggle, etc) using their individual logo in our application for getting their account information and registering them through our application. You can download the same libraries which contains two *.js files (openid-jquery.js, openid-en.js), two *.css files (openid-shadow.css, openid.css) and few images in an image folder from the following location. I have placed these libraries and files in their respective folders (Scripts, Content). These folders will get created once you install JQuery and Bootstrap files which I had done through NUGET.

image

Step 3: I have created a ~/Views/Shared folder and added a _Layout.cshtml page.

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
    <title>@ViewBag.Title</title>

    <link href="~/Content/bootstrap.min.css" rel="stylesheet" />
    <link href="~/Content/bootstrap-theme.min.css" rel="stylesheet" />
    <link href="~/Content/openid-shadow.css" rel="stylesheet" />
    <link href="~/Content/openid.css" rel="stylesheet" />
    @*<link href="~/Content/openid/Site.css" rel="stylesheet" />*@

    <a href="http://~/Scripts/jquery-2.1.1.min.js">http://~/Scripts/jquery-2.1.1.min.js</a>
    <a href="http://~/Scripts/bootstrap.min.js">http://~/Scripts/bootstrap.min.js</a>
    <a href="http://~/Scripts/openid/openid-jquery.js">http://~/Scripts/openid/openid-jquery.js</a>
    <a href="http://~/Scripts/openid/openid-en.js">http://~/Scripts/openid/openid-en.js</a>

    $(document).ready(function () {
        openid.init('openid_identifier');
    });

</head>
<body>

<div>@RenderBody()</div>

</body> </html>

Step 4: My next step is to create few models (LogOnModel.cs, RegisterModel.cs and ChangePasswordModel.cs). These models are required for log on, registering user account and changing password.

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; }
}
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; }

}
public class ChangePasswordModel
{
    [Required]
    [DataType(DataType.Password)]
    [Display(Name = "Current password")]
    public string OldPassword { get; set; }

    [Required]
    //[ValidatePasswordLength]
    [DataType(DataType.Password)]
    [Display(Name = "New password")]
    public string NewPassword { get; set; }

    [DataType(DataType.Password)]
    [Display(Name = "Confirm new password")]
    [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
    public string ConfirmPassword { get; set; }
}

Step 5: My next step is to develop some logic implementation for Validating Accounts, performing Form-based Authentication using Sign On and Sign Out options, implement membership of user where he can create, validate and get user. Hence I went upon creating classes where I can perform these implementation. Let me tell you at this stage that you don’t actually need to create these files and models if you select Account based Authentication MVC project template. Since I have selected a blank project template, I wanted to do these activities from the scratch. So I have created a Service folder and added all these interfaces and implementations.

public static class AccountValidation
{
    public static string ErrorCodeToString(MembershipCreateStatus createStatus)
    {
        // See http://go.microsoft.com/fwlink/?LinkID=177550 for
        // a full list of status codes.
        switch (createStatus)
        {
            case MembershipCreateStatus.DuplicateUserName:
                return "Username already exists. Please enter a different user name.";

            case MembershipCreateStatus.DuplicateEmail:
                return "A username for that e-mail address already exists. Please enter a different e-mail address.";

            case MembershipCreateStatus.InvalidPassword:
                return "The password provided is invalid. Please enter a valid password value.";

            case MembershipCreateStatus.InvalidEmail:
                return "The e-mail address provided is invalid. Please check the value and try again.";

            case MembershipCreateStatus.InvalidAnswer:
                return "The password retrieval answer provided is invalid. Please check the value and try again.";

            case MembershipCreateStatus.InvalidQuestion:
                return "The password retrieval question provided is invalid. Please check the value and try again.";

            case MembershipCreateStatus.InvalidUserName:
                return "The user name provided is invalid. Please check the value and try again.";

            case MembershipCreateStatus.ProviderError:
                return "The authentication provider returned an error. Please verify your entry and try again. If the problem persists, please contact your system administrator.";

            case MembershipCreateStatus.UserRejected:
                return "The user creation request has been canceled. Please verify your entry and try again. If the problem persists, please contact your system administrator.";

            default:
                return "An unknown error occurred. Please verify your entry and try again. If the problem persists, please contact your system administrator.";
        }
    }
}
public interface IFormsAuthenticationService
{
    void SignIn(string userName, bool createPersistentCookie);
    void SignOut();
}
public class FormsAuthenticationService: IFormsAuthenticationService
{
    public void SignIn(string userName, bool createPersistentCookie)
    {
        if (String.IsNullOrEmpty(userName))
            throw new ArgumentException("Value cannot be null or empty.", "userName");

        FormsAuthentication.SetAuthCookie(userName, createPersistentCookie);
    }

    public void SignOut()
    {
        FormsAuthentication.SignOut();
    }
}
public interface IMembershipService
{
    int MinPasswordLength { get; }
    bool ValidateUser(string userName, string password);

    //System.Web.Security for MembershipCreateStatus
    MembershipCreateStatus CreateUser(string userName, string password, string email, string openID);
    bool ChangePassword(string userName, string oldPassword, string newPassword);
    //System.Web.Security for MembershipUser
    MembershipUser GetUser(string openID);
}
public class MembershipService : IMembershipService
{
    private readonly MembershipProvider _provider;

    public MembershipService()
        : this(null)
    {

    }
    public MembershipService(MembershipProvider provider)
    {
        _provider = provider ?? Membership.Provider;
    }
    public int MinPasswordLength
    {
        get
        {
            return _provider.MinRequiredPasswordLength;
        }
    }

    public bool ValidateUser(string userName, string password)
    {
        if (String.IsNullOrEmpty(userName))
            throw new ArgumentException("Username cannot be null or empty");
        if (String.IsNullOrEmpty(password))
            throw new ArgumentException("Password cannot be null or empty");
        return _provider.ValidateUser(userName, password);
    }

    public MembershipCreateStatus CreateUser(string userName, string password, string email, string openID)
    {
        if (String.IsNullOrEmpty(userName))
            throw new ArgumentException("User Name cannot be null or empty");
        if (String.IsNullOrEmpty(password))
            throw new ArgumentException("Password cannot be null or empty");
        if (String.IsNullOrEmpty(email))
            throw new ArgumentException("Email cannot be null or empty");

        MembershipCreateStatus status;
        _provider.CreateUser(userName, password, email, null, null, true,
                             StringToGUID(openID), out status);
        return status;

    }

    public bool ChangePassword(string userName, string oldPassword, string newPassword)
    {
        if (String.IsNullOrEmpty(userName))
            throw new ArgumentException("User Name cannot be null or empty");
        if (String.IsNullOrEmpty(oldPassword))
            throw new ArgumentException("Old Password cannot be null or empty");
        if (String.IsNullOrEmpty(newPassword))
            throw new ArgumentException("New Password cannot be null or empty");
        try
        {
            var currentUser = _provider.GetUser(userName, true);
            return currentUser.ChangePassword(oldPassword, newPassword);
        }
        catch (ArgumentException)
        {
            return false;
        }
        catch (MembershipPasswordException)
        {
            return false;
        }

    }

    public MembershipUser GetUser(string openID)
    {
        return _provider.GetUser(StringToGUID(openID), true);
    }

    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);
    }
}

Step 6: My next step is to create two controllers, one is for the home page and the other is for Log On process. In the Log On controller I had to refer the DotnetOpenAuth libraries to pass on the OpenID information while registering the user for the first time. Here when you select a particular OpenID (like google, twitter, etc), you need to get authenticated with a return URL to their own individual interfaces. Once you are authenticated in their system, you return to the application and if you are authenticated for the first time you land up in the Account Registration page. Once your OpenID account is registered, it stores the information in our membership tables. So next time you login using OpenID account, you are not required to register again. In short the logic here is you are giving user two types of account registration process (OpenID and normal Registration).

public class LoginController : Controller
{
    private static OpenIdRelyingParty openid = new OpenIdRelyingParty();

    public IFormsAuthenticationService FormsService { get; set; }
    public IMembershipService MembershipService { get; set; }

    protected override void Initialize(RequestContext requestContext)
    {
        if (FormsService == null) { FormsService = new FormsAuthenticationService(); }
        if (MembershipService == null) { MembershipService = new MembershipService(); }

        base.Initialize(requestContext);
    }

    // **************************************
    // URL: /Account/LogOn
    // **************************************

    public ActionResult LogOn()
    {
        return View();
    }

    [HttpPost]
    public ActionResult LogOn(LogOnModel model, string returnUrl)
    {
        if (ModelState.IsValid)
        {
            if (MembershipService.ValidateUser(model.UserName, model.Password))
            {
                FormsService.SignIn(model.UserName, model.RememberMe);
                if (Url.IsLocalUrl(returnUrl))
                {
                    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);
    }

    // **************************************
    // URL: /Account/LogOff
    // **************************************

    public ActionResult LogOff()
    {
        FormsService.SignOut();

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

    // **************************************
    // URL: /Account/Register
    // **************************************

    public ActionResult Register(string OpenID)
    {
        ViewBag.PasswordLength = MembershipService.MinPasswordLength;
        ViewBag.OpenID = OpenID;
        return View();
    }

    [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 /* createPersistentCookie */);
                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);
    }

    // **************************************
    // URL: /Account/ChangePassword
    // **************************************

    [Authorize]
    public ActionResult ChangePassword()
    {
        ViewBag.PasswordLength = MembershipService.MinPasswordLength;
        return View();
    }

    [Authorize]
    [HttpPost]
    public ActionResult ChangePassword(ChangePasswordModel model)
    {
        if (ModelState.IsValid)
        {
            if (MembershipService.ChangePassword(User.Identity.Name, model.OldPassword, model.NewPassword))
            {
                return RedirectToAction("ChangePasswordSuccess");
            }
            else
            {
                ModelState.AddModelError("", "The current password is incorrect or the new password is invalid.");
            }
        }

        // If we got this far, something failed, redisplay form
        ViewBag.PasswordLength = MembershipService.MinPasswordLength;
        return View(model);
    }

    // **************************************
    // URL: /Account/ChangePasswordSuccess
    // **************************************

    public ActionResult ChangePasswordSuccess()
    {
        return View();
    }

    [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();
                    return new WrapperHttpResponseMessageResult(request.RedirectingResponse);
                }
                catch (ProtocolException ex)
                {
                    ViewBag.Message = ex.Message;
                    return View("Index");
                }
            }

            ViewBag.Message = "Invalid identifier";
            return View("Index");
        }

        //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(@"~/Views/Home/Index.cshtml", lm);

            case AuthenticationStatus.Canceled:
                ViewBag.Message = "Canceled at provider";
                return View("Index");
            case AuthenticationStatus.Failed:
                ViewBag.Message = response.Exception.Message;
                return View("Index");
        }

        return new EmptyResult();
    }
}

Step 7: Once you are done with the controllers, now it is time to create your views. Here I went on to create two views, one having OpenID and normal Login page and the other having Account registration page.

@model DotnetOpenAuth.Demo.Models.LogOnModel
@{
    ViewBag.Title = "Index";
    Layout = "~/Views/Shared/_Layout.cshtml";
}
<a href="http://~/Scripts/jquery.validate.min.js">http://~/Scripts/jquery.validate.min.js</a>
<a href="http://~/Scripts/jquery.validate.unobtrusive.min.js">http://~/Scripts/jquery.validate.unobtrusive.min.js</a>
<section/>

<div class="container">

&nbsp;

<div class="row">

&nbsp;

<h2>DotnetOpenAuth Demo System</h2>

&nbsp;

</div>

@using (Html.BeginForm("Authenticate", "Login", new { ReturnUrl = @HttpUtility.UrlEncode(Request.QueryString["ReturnUrl"]) }, FormMethod.Post, new { id = "openid_form" })) { <input type="hidden" name="action" value="verify" />

<div class="row">

&nbsp;

<div class="col-md-12">

&nbsp;

Please Select you Account Provider

&nbsp;

</div>

&nbsp;

<div id="openid_btns" class="col-md-12"></div>

</div>

<div class="row">

&nbsp;

<div id="openid_input_area" class="col-md-12">

&nbsp;

@Html.TextBox("openid_identifier", new { @class = "form-control", @style = "width:300px;" })

&nbsp;

</div>

&nbsp;

<div class="col-md-12">

@if (Model != null) { if (String.IsNullOrEmpty(Model.UserName)) {

<div class="label label-primary">@Html.LabelFor(model => model.OpenID)</div>

&nbsp;

<div class="text-box">@Html.DisplayFor(model => model.OpenID)</div>

&nbsp;

<div>

&nbsp;

@Html.ActionLink("New User ,Register", "Register", new { OpenID = Model.OpenID, @class = "btn btn-primary" })

&nbsp;

</div>

} else { <blockquote> <p> <a href="@Url.Action("Index", "Home")"> Welcome , @Model.UserName, Continue..." </a> </p> </blockquote> } } </div> </div> } @Html.ValidationSummary(true, "Login was unsuccessful. Please correct the errors and try again.") @using (Html.BeginForm()) {

<div>

Or Login Normally

<div class="row">

&nbsp;

&nbsp;

<div>@Html.LabelFor(m => m.UserName, new { @class = "col-sm-2 control-label" })</div>

&nbsp;

<div class="col-sm-10">@Html.TextBoxFor(m => m.UserName, new { @class = "form-control", @style = "width:300px;" }) @Html.ValidationMessageFor(m => m.UserName)</div>

</p> </div>

<div class="row">

&nbsp;

&nbsp;

<div>@Html.LabelFor(m => m.Password, new { @class = "col-sm-2 control-label" })</div>

&nbsp;

<div class="col-sm-10">@Html.PasswordFor(m => m.Password, new { @class = "form-control", @style = "width:300px;" }) @Html.ValidationMessageFor(m => m.Password)</div>

</p> </div>

<div class="row">

&nbsp;

&nbsp;

<div class="col-md-10">

&nbsp;

<div>@Html.CheckBoxFor(m => m.RememberMe) @Html.LabelFor(m => m.RememberMe)</div>

</div> </p> </div>

<div class="row">

&nbsp;

&nbsp;

<div class="col-md-10"></div>

</p> </div> </fieldset> </div> } </div> </section>
@model DotnetOpenAuth.Demo.Models.RegisterModel

@{
    ViewBag.Title = "Register";
    Layout = "~/Views/Shared/_Layout.cshtml";
}
<a href="http://~/Scripts/jquery.validate.min.js">http://~/Scripts/jquery.validate.min.js</a>
<a href="http://~/Scripts/jquery.validate.unobtrusive.min.js">http://~/Scripts/jquery.validate.unobtrusive.min.js</a>

<section></pre>
<div class="container">

&nbsp;
<div class="row">

&nbsp;
<h2>Register your Account</h2>

&nbsp;
</div>

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

Account Information
<div class="row">

&nbsp;

@if (ViewData["OpenID"] != null) {
<div>@Html.Label("OpenID", new { @class = "col-sm-2 control-label" })</div>

&nbsp;
<div class="col-sm-10">@Html.Label((string)ViewBag.OpenID, new { @class = "col-sm-2 control-label" })</div>

} 
</div>
<div class="row">

&nbsp;

&nbsp;
<div>@Html.LabelFor(m => m.UserName, new { @class = "col-sm-2 control-label" })</div>

&nbsp;
<div class="col-sm-10">@Html.TextBoxFor(m => m.UserName, new { @class = "form-control", @style = "width:300px;" }) @Html.ValidationMessageFor(m => m.UserName)</div>

</div>
<div class="row">

&nbsp;

&nbsp;
<div>@Html.LabelFor(m => m.Email, new { @class = "col-sm-2 control-label" })</div>

&nbsp;
<div class="col-sm-10">@Html.TextBoxFor(m => m.Email, new { @class = "form-control", @style = "width:300px;" }) @Html.ValidationMessageFor(m => m.Email)</div>

</div>
<div class="row">

&nbsp;

&nbsp;
<div>@Html.LabelFor(m => m.Password, new { @class = "col-sm-2 control-label" })</div>

&nbsp;
<div class="col-sm-10">@Html.PasswordFor(m => m.Password, new { @class = "form-control", @style = "width:300px;" }) @Html.ValidationMessageFor(m => m.Password)</div>

</div>
<div class="row">

&nbsp;

&nbsp;
<div>@Html.LabelFor(m => m.ConfirmPassword, new { @class = "col-sm-2 control-label" })</div>

&nbsp;
<div class="col-sm-10">@Html.PasswordFor(m => m.ConfirmPassword, new { @class = "form-control", @style = "width:300px;" }) @Html.ValidationMessageFor(m => m.ConfirmPassword)</div>

</div>
<div class="row">

&nbsp;

&nbsp;
<div class="col-md-10"></div>

</div>
</fieldset>
</div>

 } </div>
</section>

Step 8: Till now everything seems good. Now its time to run the application and see how it behaves. Well unfortunately I got the below error during runtime.

Attempt by security transparent method ‘DotNetOpenAuth.Messaging.MessagingUtilities.AsActionResult(DotNetOpenAuth.Messaging.OutgoingWebResponse)’ to access security critical type ‘System.Web.Mvc.ActionResult’ failed.

Assembly ‘DotNetOpenAuth.Core, Version=4.3.0.0, Culture=neutral, PublicKeyToken=2780ccd10d57b246’ is marked with the AllowPartiallyTrustedCallersAttribute, and uses the level 2 security transparency model.  Level 2 transparency causes all methods in AllowPartiallyTrustedCallers assemblies to become security transparent by default, which may be the cause of this exception.

Looking to this error it seems that DotNetOpenAuth is not working with MVC 5. This might be because DotNetOpenAuth has not been upgraded for MVC 5. This issue is coming from Authenticate method of Login Controller. Anyway I had to do few things in order to resolve the issue. I have created a helper which inherits ActionResult named WrapperHttpResponseMessageResult.

public class WrapperHttpResponseMessageResult : ActionResult
{
    private readonly OutgoingWebResponse _response;

    public WrapperHttpResponseMessageResult(OutgoingWebResponse response)
    {
        _response = response;
    }

    public override void ExecuteResult(ControllerContext context)
    {
        HttpResponseBase responseContext = context.RequestContext.HttpContext.Response;
        responseContext.StatusCode = (int)_response.Status;
        responseContext.StatusDescription = _response.Status.ToString();
        foreach (string key in _response.Headers.Keys)
        {
            responseContext.AddHeader(key, _response.Headers[key]);
        }

        if (_response.Body != null)
        {
            StreamWriter escritor = new StreamWriter(responseContext.OutputStream);
            escritor.WriteAsync(_response.Body).Wait();
        }
    }
}

In my Authenticate() method, I had to change  “return request.RedirectingResponse.AsActionResult();” to “return new WrapperHttpResponseMessageResult(request.RedirectingResponse);”. This is just a hack to resolve the issue. Well it worked.

Now when the application is running I can see the below page. I clicked on Google and it redirected me to Google log on page.

image

Once I have logged in to Google, I have been redirected to registering this OpenID account page. This will be one time registration. Once registered next time you will not be redirected to this page since the account is now saved in our membership table.

image

That’s it I got my account registered with successful log on to application. You can review the below blog for testing OAuth which I feel might be useful for us. http://scatteredcode.wordpress.com/2011/12/21/testing-oauth-apis-without-coding/

You can download the source code which has all these implementation and tested properly.

Download Source

Leave a Reply

%d bloggers like this: