July 18, 2008

Adding OpenID to your web site in conjunction with ASP.NET Membership

AspDotNetMVC_OpenIdLoginI recently added membership, accounts, login, etc. to the AspDotNetMVC site. While doing so I decided I wanted to support OpenID, too. However, I didn't want to go with only OpenID because I needed ASP.NET Membership in place to work in conjunction with another application, a Kigg site used as a service for rating content on the AspDotNetMVC site. I could have probably converted the Kigg code base to use OpenID but I also wanted to allow people who may not have OpenID to create traditional accounts on the site without signing up for OpenID. Following are the steps I took to implement an OpenID login and integrate it with "traditional ASP.NET" membership. Follow these six steps and you can do the same.

1. Download the awesome C# OpenID library, DotNetOpenId from Google Code, and add it as a reference in your project. This is a great open source library developed by Andrew Arnott, Jason Alexander, Scott Watermasysk, Scott Hanselman, Joe Brinkman and others. It seriously does all the heavy lifting, comes with some good examples, and Andrew Arnott is doing a great job of making posts about fringe cases on his blog.

2. Add some nice usability features to your OpenID login. Go to ID Selector, create an account and grab the couple of lines of JavaScript to create the cool OpenID service selector shown in the image at right.

Did I mention that this is so easy that I don't know why everyone isn't supporting OpenID? Going forward I will be doing so in every new site that I create and I'll retrofit existing sites when I get the chance.AspDotNetMVC_OpenIdLogin2

3. Read the following articles:

After reading the above you will have a pretty good understanding of how to add OpenID to your site. Some of the code examples in the earlier 3 posts by Andrew are a little out of date. As of the writing of this post the code provided in Hanselman's blog post are the most up to date. Go ahead and read the two "MVC" posts by Andrew even if you are not interested in ASP.NET MVC. The concepts displayed in those posts can be used anywhere. In fact, it was in of those posts where I found my inspiration for overcoming my next hurdle.

The only thing that was missing for me was integration with ASP.NET membership. I actually found very little information for accomplishing this. I found quite a lot of questions asking how to do it, but most of the answers were just more questions asking why you would want to do it.

4. Create your login form (or user control or composite server control or whatever you prefer). In my case I created a user control that holds just the fields necessary for OpenID login and the logic behind them. Doing so allows me to drop that control into any existing login page (shown in image above) or my existing create account page or anywhere else I want to use it. Make sure you add the javascript for the ID Selector.

For allowing traditional ASP.NET membership login and user account creation I just dropped the basic out of the box controls on the page.

5. Wire up the login submit button. I did something like below, which is very similar to examples that Andrew and Scott Hanselman provided. Basically I'm just telling the OpenID provider that I need the user's email and nickname (for use in the next step):

protected void loginButton_Click(object sender, EventArgs e) {
   if (!openidValidator.IsValid) return; // don't login if custom validation failed.  
   OpenIdRelyingParty openid = new OpenIdRelyingParty();
   try {
      IAuthenticationRequest request = openid.CreateRequest(openid_identifier.Text);
      ClaimsRequest fetch = new ClaimsRequest();
      fetch.Nickname = DemandLevel.Require;
      fetch.Email = DemandLevel.Require;
      request.AddExtension(fetch);
      request.RedirectToProvider();
   } catch (OpenIdException ex) {
      // The user probably entered an Identifier that   
      // was not a valid OpenID endpoint.  
      openidValidator.Text = ex.Message;
      openidValidator.IsValid = false;
   }
}

6. Handle the response from the OpenID provider. This snippet of code is part of the login process and expands upon examples from Andrew Arnott's and Scott Hanselman's posts linked to previously.

Here I'm pulling the alias and email address that I requested in the previous step. With that information I check to see if the user already exists in the ASP.NET membership datastore. If not I will create a Membership account for them using their OpenID URI as their username. When I create the account you can see that I'm generating a random string for the user's password field and password answer field. You'll also see that I'm adding "This is an OpenID account. You should log in with your OpenID." as the password question. That way if a user forgets they used OpenID and tries to login through the traditional username/password login form and selects that they forgot their password when the login doesn't work, he or she will get a reminder about their OpenID in the form of the password question. I know it's a hack, but it works for now for my modest needs and I'm happy with it.

Next you'll see that after I create the Membership User I then set the user's "comment" field to their Nickname. This is because the "Hello, Username. Welcome back to the site." message at the top of the page would normally display the user's "username". In the case of OpenID users their username would be their OpenID URI - something like: http://danhounshell.openidprovidername.com/. That's a bit ugly and I'd rather use the Nickname that I asked from then when they logged in with OpenID. By shoving the Nickname into the comments field I can first check to see if it has a value and if so use it in the display rather than their username. If it doesn't have a value then I can default to the username. So now using my previous example it will say "Hello, DanHounshell. Welcome back to the site." This same thing could be accomplished in a couple of other ways. Throwing the nickname into the session object or putting it in a cookie are other reasonable alternatives. In my case I don't use the comments field for anything else so once again - I know it's a hack, but it works for now for my modest needs and I'm happy with it. Plus it's easy to get to by just writing "user.Comment". Thinking about it, it might be nice to write an extension method for MembershipUser called DisplayName that determines whether to use the user.Comment property or the user.Username property.

Finally, I just call FormsAuthentication.RedirectFromLoginPage() passing in their OpenID URI that they provided, logging them into the site.

case AuthenticationStatus.Authenticated:
   ClaimsResponse fetch = openid.Response.GetExtension(typeof(ClaimsResponse)) as ClaimsResponse;
   string alias = fetch.Nickname;
   string email = fetch.Email; 
 
   if (string.IsNullOrEmpty(alias))
      alias = openid.Response.ClaimedIdentifier;
   if (string.IsNullOrEmpty(email))
      email = openid.Response.ClaimedIdentifier;
 
   //Now see if the user already exists, if not create them
   if (Membership.GetUser(openid.Response.ClaimedIdentifier) == null) 
   {
      MembershipCreateStatus membershipCreateStatus;
      MembershipUser user = Membership.CreateUser(openid.Response.ClaimedIdentifier, 
         Common.GetRandomString(5,7), 
         email, 
         "This is an OpenID account. You should log in with your OpenID.", 
         Common.GetRandomString(5,7), 
         true, 
         out membershipCreateStatus);
       if (membershipCreateStatus != MembershipCreateStatus.Success) 
      {
          loginFailedLabel.Text += ": Unsuccessful creation of Account: " + membershipCreateStatus.ToString();
          loginFailedLabel.Visible = true;
          break;
       }
       user.Comment = alias;
       Membership.UpdateUser(user);
    }
    // Use FormsAuthentication to tell ASP.NET that the user is now logged in,  
    // with the OpenID Claimed Identifier as their username. 
    FormsAuthentication.RedirectFromLoginPage(openid.Response.ClaimedIdentifier, chkRememberMe.Checked);
    break;

That's all there is to it. Now you can allow your user's to choose whether they'd like to create an account on your site by creating a new username and password or by using their new or existing OpenID. The bonus with this method is that it allows you to add OpenID support to an existing site that already has traditional membership without breaking anything.

Enjoy and let me know if you have any questions or comments.

Update: October 09, 2008 -  I posted a sample web application that uses the OpenID login control described in this post.

kick it on DotNetKicks.com

Comments,

  • Trackbacks,
  • and Pingbacks
  1. Wicked sweet post Dan. Really helps me address questions I've had with ASP.NET and OpenID. Thanks much for sharing.

    Do you have any plans to reason a sample/example project? I just a bit curious where and how exactly you "handle the response from OpenID?" I don't see a callback URL.

    Once again, great post. Very informative.

  2. Justin, check out Andrew Arnott's example in this post:

    blog.nerdbank.net/.../how-to-add-openid-to-your-aspnet-forms.html

    I'm doing the exact same thing in my Page_Load, checking to see if the request is a response from the OpenID provider (the second code snippet above is an extension of that same code). The DotNetOpenID library must handle sending the callback Url and gathering the response.

  3.  avatar Jeff says:

    Good article. Nice reference material. Thanks.

    Any chance you care to share the user control and code you created for the login form?

  4.  avatar Jeff says:

    Dan, are you using roles at all as well and if so have you noticed any issues between OpenId and the roles and roles provider?

  5. Jeff, not for everyone who signs up. In my case as long as the user is authenticated is all that I care about.

    I am using roles specifically for my account using OpenId, though. I have not noticed any issues. I am able to see extended functionality (only available to admins) when I login with that account.

    Sure, I'll share the user control I created for my OpenId login form. It's nothing fancy and I showed most of the code in the article above, but I will zip it up and share it for completeness sake. I'll append the article with a link to a zip file when I get a chance to scrub it, package it up, and upload it.

  6.  avatar Jeff says:

    Have you had a chance to set up a demo project with the login user control you created?

  7.  avatar Jeff says:

    Hey Dan,

    Where can I find Common.GetRandomString?

    Is this from a library you've written personally or where is it at?

  8. Jeff, I have not put together a sample project, but it shouldn't take long to do so. I'll try to squeeze that in this weekend.

    Common.GetRandomString(minLength, maxLength) is just a library function that I wrote for that project. Nothing special, but I will include it in the sample project.

  9.  avatar Dave Burke says:

    This is really excellent work, Dan. I've been really debating on whether to go with ASPNET Membership or OpenID for some upcoming projects and your work makes the decision a whole lot easier.  Thank you so much!

  10. Thank you, Dave!

  11.  avatar Scott says:

    Thank you for this.  Its exactly what I was looking for and I really appreciate the sample you put out.

  12. Nice post Dan, special thanks for linking to all of the other relevant content.

  13.  avatar Tuan says:

    I'm so glad I found this post. Thanks, Dan.

  14.  avatar Kevin Curry says:

    FYI, IDSelector has been "upgraded" to RPXNow.  Doesn't seem as straightforward as you describe w/ IDSelector

    Good post.

  15.  avatar Peeyush says:

    Hi,

    I followed the steps as mentioned, and everthing worked fine except for the "DotNetOpenAuth.Contracts" library. It gives an error:

    "Could not load file or assembly 'DotNetOpenAuth.Contracts' or one of its dependencies. Strong name signature could not be verified.  The assembly may have been tampered with, or it was delay signed but not fully signed with the correct private key. (Exception from HRESULT: 0x80131045) "

    So, I have excluded the file from project, and it works fine. I had to replace OpenIdException with a Exception. Any help out there ?

    Thanks

  16. Nice Post

    Informative and useful one

    I am .Net Developer and I am looking for this

    Thanks for the great stuff.

  17.  avatar gaurav says:

    Hello Dan,

    Can you help me implementing OpenID on ASP.net

    Thanks for your time and consideration,

    Best,

    Gaurav

    gloyalka@gmail.com

Comments are closed.

 

Trackbacks and Pingbacks


  1. Dave Burke says:

    Everyman Links for July 20, 2008

  2. Pingback from  Dew Drop - July 20, 2008 | Alvin Ashcraft's Morning Dew

  3. TrackBack says:
  4. Pingback from  Reflective Perspective - Chris Alcock  » The Morning Brew #140

  5. You've been kicked (a good thing) - Trackback from DotNetKicks.com

  6. TrackBack says:
  7. DCSHosts.com says:

    Adding OpenID to your web site in conjunction with ASP.NET Membership...

  8. TrackBack says:
  9. Pingback from  Blue Onion Software - Onion Peels Blog - Friday Links #9

  10. Here is the latest in my link-listing series .  Also check out my ASP.NET Tips, Tricks and Tutorials

  11. Here is the latest in my link-listing series .  Also check out my ASP.NET Tips, Tricks and Tutorials

  12. Pingback from  Bookmarks about Openid

  13. This blog carnival will be entirely dedicated to web development in MS platform. Scott has published

  14. Pingback from  Enlaces de Octubre: ASP.NET, ASP.NET MVC, ASP.NET Dynamic Data « Thinking in .NET

  15. A couple of months ago I posted how to add OpenID to your existing web site in conjunction with ASP.NET

  16. This week’s web nuggets is going to be a double dose – last week was busy, busy, busy! Pick of the week: 10 Programming Proverbs Every Developer Should Know General Windows Coming to Amazon EC2 : Windows developers will soon be able to take advantage

  17. Programming says:

    Here is the latest in my link-listing series .  Also check out my ASP.NET Tips, Tricks and Tutorials

  18. Pingback from  Recent Links Tagged With "openid" - JabberTags

  19. Pingback from  Converting the ASP.NET MVC project into OpenID « Temporarily unnamed blog

  20. Integrate Google friend connect (GFC) with existing asp.net membership and website

  21. Integrate Google friend connect (GFC) with existing asp.net membership and website

  22. Pingback from ASP.NET Membership Provider usage in conjunction with an MV | user75

  23. Pingback from ASP.NET Membership Provider usage in conjunction with an MV | user75

  24. Pingback from Flexmind Solutions | Login to SharePoint 2010 site using Gmail ID

  25. Pingback from OpenID reference problem in asp.net | Jptab

  26. A. Ber says:

    Adding OpenID to your web site in conjunction with ASP.NET Membership : Digging My Blog

Shortcuts

Where is Dan?


My Blog
My Blog
Twitter
Twitter
Facebook
Facebook
LinkedIn
LinkedIn
Flickr
Flickr
YouTube
YouTube
Delicious
Delicious
Foursquare
Foursquare
Pinterest
Pinterest
GetGlue
GetGlue
Pintley
Pintley
XBOX Live
XBOX Live
Last.fm
Last.fm
Windows Live
Windows Live
Telligent.com
Telligent
Graffiti CMS on CodePlex
Graffiti CMS
Popular

Recent Posts