ASP.NET MVC 2 and OpenID

by 5. August 2010 01:10

In this post I’ll be showing you how to integrate OpenID authentication on your ASP.NET MVC 2 site. To this end, I’ll be integrating DotNetOpenAuth and openid-selector, so go ahead and download the latest versions of both and extract them to a folder of your choosing.

First, open Visual Studio 2010 and create a new project by going to File > New > Project > Web > ASP.NET MVC 2 Application:

newproject

Click “OK” and you will be asked if you want to create a unit test project or not. For the purpose of this article I’ll select no, but feel free to create one.

Now, after Visual Studio is done creating your project, you can hit F5 and you should see this page in your browser:

initialpage

This is actually a fully functioning ASP.NET MVC site, but our aim is to replace the default “Log On” mechanism with an OpenID approach. To do this, we’ll be integrating DotNetOpenAuth as mentioned earlier. DotNetOpenAuth is a free library that allows you to integrate OpenID by simply adding an ASP.NET control to your page.

To use DotNetOpenAuth, right click “References” in the Solution Explorer, choose “Add Reference” and browse to where you extracted DotNetOpenAuth to. Go to the bin folder, select “DotNetOpenAuth.dll” and click “Open”, then “Add” and finally “Close”. Now we are almost ready to use DotNetOpenAuth, but first we need to add some lines to the Web.config file.

Add the following lines inside the <configuration> element in your Web.config file:

<configSections>
<section name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection" requirePermission="false" allowLocation="true"/>
</configSections>
<uri>
<idn enabled="All"/>
<iriParsing enabled="true"/>
</uri>
<dotNetOpenAuth>
<openid>
<relyingParty>
<security requireSsl="false"/>
<behaviors>
<!-- The following OPTIONAL behavior allows RPs to use SREG only, but be compatible
with OPs that use Attribute Exchange (in various formats). -->
<add type="DotNetOpenAuth.OpenId.Behaviors.AXFetchAsSregTransform, DotNetOpenAuth"/>
</behaviors>
</relyingParty>
</openid>
<messaging>
<untrustedWebRequest>
<whitelistHosts>
<!-- since this is a sample, and will often be used with localhost -->
<add name="localhost"/>
</whitelistHosts>
</untrustedWebRequest>
</messaging>
<!-- Allow DotNetOpenAuth to publish usage statistics to library authors to improve the library. -->
<reporting enabled="true"/>
</dotNetOpenAuth>

Now we are all set to use DotNetOpenAuth, but first we’ll integrate openid-selector. openid-selector is a simple JavaScript, where users instead of entering their full OpenID identifier, simply clicks the logo of their OpenID provider and then enter their username. Luckily it is really simple to integrate.

There are 4 steps to integrate openid-selector with your ASP.NET MVC 2 project:

  1. go to where you extracted it in the first place and open the css folder. Inside this you’ll find a file named openid.css. Copy the contents of this file and insert it at the end of the Site.css file in your Visual Studio project under the “Content” folder.
  2. Add a new folder to your project called “images”. Then, right click it and select “Add” > “Existing item…” and then browse to the openid-selector/images folder. Select all the files and click “Add”.
  3. In your Visual Studio project, right click the “Scripts” folder and select “Add” > “Existing Item…”  and then browse to the openid-selector/js folder, select the “openid-jquery.js” file and click “Add”. Before moving on, you need to open the newly added “openid-jquery.js” file and scroll down to the line (line number 88 in the version I tested):

    img_path: 'images/',

    and replace it with the line:

    img_path: '../images/',
  4. Finally, go to “Views” > “Shared” and open the “Site.Master” file and insert the following lines in the <head>-tag:
    <script type="text/javascript" src="../../Scripts/jquery-1.4.1.min.js"></script>
    <script type="text/javascript" src="../../Scripts/openid-jquery.js"></script>
    <script type="text/javascript">
    $(document).ready(function () {
    openid.init('openid_identifier');
    });
    </script>

Your project should now look something like this in the Solution Explorer:

folderstructure

Now, after all the configuration is done, it’s finally time to setup the OpenID integration. Open the “LogOn.aspx” in “Views” > “Account” and replace the <asp:Content ID=”loginContent”..> with this:

<asp:Content ID="loginContent" ContentPlaceHolderID="MainContent" runat="server">
<h2>Log On</h2>
<p>
Please enter your username and password. <%: Html.ActionLink("Register", "Register") %> if you don't have an account.
</p>
<form action="Authenticate?ReturnUrl=<%=HttpUtility.UrlEncode(Request.QueryString["ReturnUrl"]) %>" method="post" id="openid_form">
<input type="hidden" name="action" value="verify" />
<%: Html.ValidationSummary(true, "Login was unsuccessful. Please correct the errors and try again.") %>
<div>
<fieldset>
<legend>Account Information</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>
</fieldset>
</div>
</form>
</asp:Content>

Click F5 and browse to the “Log On” page, and you should see this:

logon

Almost done now. Now we just need to hook up the Controller, so back in Visual Studio, expand “Controllers” and open AccountController.cs.

First we need to add several using statements, in order to use the DotNetOpenAuth library:

using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OpenId;
using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration;
using DotNetOpenAuth.OpenId.RelyingParty;

Next, in the top of your class, add this static variable:

private static OpenIdRelyingParty openid = new OpenIdRelyingParty();

And then add the following method:

[ValidateInput(false)]
public ActionResult Authenticate(string returnUrl)
{
var response = openid.GetResponse();
if (response == null)
{
// Stage 2: user submitting Identifier
Identifier id;
if (Identifier.TryParse(Request.Form["openid_identifier"], out id))
{
try
{
var request = openid.CreateRequest(Request.Form["openid_identifier"]);
//Ask user for their email address
ClaimsRequest fields = new ClaimsRequest();
fields.Email = DemandLevel.Request;
request.AddExtension(fields);
return request.RedirectingResponse.AsActionResult();
}
catch (ProtocolException ex)
{
ViewData["Message"] = ex.Message;
return View("LogOn");
}
}
ViewData["Message"] = "Invalid identifier";
return View("LogOn");
}
// Stage 3: OpenID Provider sending assertion response
switch (response.Status)
{
case AuthenticationStatus.Authenticated:
MembershipUser user = MembershipService.GetUser(response.ClaimedIdentifier);
if (user == null)
{
MembershipCreateStatus membershipCreateStatus;
//Get custom fields for user
var sreg = response.GetExtension<ClaimsResponse>();
if (sreg != null)
membershipCreateStatus = MembershipService.CreateUser(response.ClaimedIdentifier, "12345", sreg.Email);
else
membershipCreateStatus = MembershipService.CreateUser(response.ClaimedIdentifier, "12345", "john@doe.com");
if (membershipCreateStatus == MembershipCreateStatus.Success)
{
FormsService.SignIn(response.ClaimedIdentifier, false /* createPersistentCookie */);
return RedirectToAction("Index", "home");
}
ViewData["Message"] = "Error creating new user";
return View("LogOn");
}
FormsAuthentication.SetAuthCookie(user.UserName, false);
if (!string.IsNullOrEmpty(returnUrl))
{
return Redirect(returnUrl);
}
return RedirectToAction("Index", "Home");
case AuthenticationStatus.Canceled:
ViewData["Message"] = "Canceled at provider";
return View("LogOn");
case AuthenticationStatus.Failed:
ViewData["Message"] = response.Exception.Message;
return View("LogOn");
}
return new EmptyResult();
}

Before this will compile, you need to make a change to the “Models” > “AccountModels.cs” file.

Add this line to the IMembershipService interface:

MembershipUser GetUser(string userName);

and then add the method implementation to  the AccountMembershipService class:

public MembershipUser GetUser(string userName)
{
MembershipUser currentUser = _provider.GetUser(userName, false /* userIsOnline */);
return currentUser;
}

And we’re done! Note: this code is based on the provided samples you get when downloading DotNetOpenAuth, and it is only a guide to get it setup with an ASP.NET MVC 2 project, not a ready-for-production solution.

Tags: , , , ,

blog comments powered by Disqus