The Weekly Source Code 25 - OpenID Edition
We spent a lot of time at Corillian (my last job) thinking about Identity, and a few months before I left I started getting into Cardspace and OpenID. This was a little over a year ago. We did a podcast on OpenID as well.
At that time, I tried to take the only .NET implementation at the time of OpenID which was written in in Boo written originally by Grant Monroe and port it to C# causing me to go through the Programmer Phases of Grief. Andrew Arnott and Jason Alexander took the reins and we spend a number of late nights trying to get a good OpenID library working. I gave up, but they soldiered on and created dotnetopenid (downloads), including a client and server as well as Andrew's excellent ASP.NET controls .
Fast-forward to now. My new friend Aaron Hockley decided to a stands and promote OpenID. He said:
Effective immediately, I will no longer comment on tech blogs that don’t support OpenID for comment authentication.
He's just one guy, but his heart is in the right place. He points out that:
Google offers it as a Blogger option. It’s available as a super-easy-to-install WordPress plugin. Movable Type has it as a built-in feature.
OpenID is a good thing and it's Growing. You may already have an OpenID if, for example, you have a Yahoo! account. More on this soon.
How to turn your blog into an OpenID
Simon Willison wrote How to turn your blog into an OpenID and it's very easy.
STEP 1: Get an OpenID. There a lots of servers and services out there you can use. I use http://www.myopenid.com for two reasons. One, I know the CEO (they're in Portland), and two, they support optionally using CardSpace to authenticate yourself (as well as the standard way with password).
STEP 2: Add these two lines to your blog's main template in-between the <HEAD></HEAD> tags at the top of your template. Most all blog engines support editing your template so this should be an easy and very possible thing to do.
Example:
<link rel="openid.server" href="http://www.myopenid.com/server" />
<link rel="openid.delegate" href=http://YOURUSERNAME.myopenid.com/ />
This will let you use your domain/blog as your OpenID. Now, I can log in with "http://www.hanselman.com" when I see an OpenID Login option - and so can you! Go do it now!
Making OpenID Logins Easier
If you have a blog or site with OpenID support, you should go get this little snippet of JavaScript and install an OpenID ID Selector on your blog from http://www.idselector.com/.
One of the things that is slowing OpenID adoption is that many people don't realize that they may already have one. That's what this little Javascript is trying to do by showing folks sites that they recognize. This way my Dad could login using Yahoo and it would make sense to him. It's a little busy, but it's a start. I've added an http://www.idselector.com/ to my blog for comments.
Adding OpenID Support to DasBlog
A year ago, I originally tried to port the Boo code to C# in an attempt to enable OpenID in DasBlog but eventually gave up. However, last night, I re-familiarized myself with the OpenID spec (it's on 2.0 now) and started reading the source for http://code.google.com/p/dotnetopenid/.
In a word, it's a joy. I was able to get OpenID running OK in two hours and working well and up on my blog in two more. I have to give credit to the fantastic work that Andrew Arnott and Jason Alexander and team are doing. It's come far and you should know about it, Dear Reader.
I had two scenarios in DasBlog (again, in case you didn't know, it's the C# and XML-based blog that runs this site and others) to handle.
First, I wanted to support OpenID for Comments which wouldn't actually "log a user in" in the stateful FormsAuthentication sense. I think this isn't a very common scenario, and I'd describe it as One-Time Occasional Authentication. In this case, I used the dotnetopenid classes directly in a moderately complex scenario.
Second, I wanted to support OpenID to login as the Administrator for my site. This would, in fact, log me in via FormsAuthentication. This would be a common scenario that you'd probably care about as it's very typical. In this case, I used the dotnetopenid ASP.NET Controls, which were about as easy as falling off a log. (That's pretty easy.)
Here's the first, harder, scenario.
If you've entered your OpenID and hit Submit Comment then we'll store the current entry and the comment you're submitting. We'll be redirecting away to get authenticated and we'll need them when we get back. If you're running in a WebFarm, you'll want to store these temporary variables in a database or somewhere that doesn't have node-affinity.
Session["pendingComment"] = comment.Text;
Session["pendingEntryId"] = ViewState["entryId"] as string;
OpenIdRelyingParty openid = new OpenIdRelyingParty();
IAuthenticationRequest req = openid.CreateRequest(openid_identifier.Text);
ClaimsRequest fetch = new ClaimsRequest();
fetch.Email = DemandLevel.Require;
fetch.Nickname = DemandLevel.Require;
req.AddExtension(fetch);
SaveCookies();
req.RedirectToProvider();
return;
What I think of as an "OpenID Client" is called a "Relying Party" or "RP" in the parlance of the OpenID folks. In this code we create an AuthenticationRequest and add some additional claims. There's a nice interface-based extension model in this lower-level library that lets you Request or Require information from the user's profile. For comments on the blog, I just need your email for your Gravatar and your Nickname for Display.
I then call RedirectToProvider, and that's if for the request side. Remember I said this was the hard scenario! Not so hard. ;)
Next, we're redirected to an OpenIDProvider, we authenticate (or not) and are redirected BACK with additional information encoded on the GET. On the way back in, in our Page_Load (or an HttpHandler if you like) we check the Response status. If we're Authenticated, we grab the info we requested and add the comment. Bam. Sprinkle in a little error handling and we're all set.
OpenIdRelyingParty openid = new OpenIdRelyingParty();
if (openid.Response != null)
{
// Stage 3: OpenID Provider sending assertion response
switch (openid.Response.Status)
{
case AuthenticationStatus.Authenticated:
ClaimsResponse fetch = openid.Response.GetExtension(typeof(ClaimsResponse)) as ClaimsResponse;
string nick = fetch.Nickname;
string homepage = openid.Response.ClaimedIdentifier;
string email = fetch.Email;
string comment = Session["pendingComment"] as string;
string entryId = Session["pendingEntryId"] as string;
if (String.IsNullOrEmpty(comment) == false && String.IsNullOrEmpty(entryId) == false)
{
AddNewComment(nick, email, homepage, comment, entryId, true);
}
break;
}
}
Here's the second scenario where we'll log in as the Administrator of the blog. I just register the DotNetOpenId assembly in my ASPX page and put an <openidlogin> control on the page. Notice that even the claims I created in the manual scenario above are just properties on this control. There's also events like OnLoggedIn to handle the results.
<%@ Register Assembly="DotNetOpenId" Namespace="DotNetOpenId.RelyingParty" TagPrefix="cc1" %>
<cc1:openidlogin id="OpenIdLogin1"
RequestEmail="Require" RequestNickname="Request" RegisterVisible="false"
RememberMeVisible="True" PolicyUrl="~/PrivacyPolicy.aspx" TabIndex="1"
OnLoggedIn="OpenIdLogin1_LoggedIn"/></cc1:openidlogin>
This controls renders nicely as seen in the screenshot below.
In the OnLoggedIn event, I call my existing security APIs (Thanks to Tony Bunce and Anthony Bouch) and set the AuthCookie from FormsAuthentication.
protected void OpenIdLogin1_LoggedIn(object sender, OpenIdEventArgs e)
{
UserToken token = SiteSecurity.Login(e.Response);
if (token != null)
{
FormsAuthentication.SetAuthCookie(userName, rememberCheckbox.Checked);
Response.Redirect(SiteUtilities.GetAdminPageUrl(), true);
}
}
Poof. I love using well designed libraries and just work. At this point all that was left was adding some CSS and tidying up.
OpenID and ASP.NET WebForms and MVC
The dotnetopenid source includes source for sample sites. It actually includes three samples, two WebForms and one ASP.NET MVC.
The MVC implementation is very clean, even though (or because?) it doesn't use controls. Here's the Authenticate Controller Action:
public void Authenticate() {
var openid = new OpenIdRelyingParty();
if (openid.Response == null) {
// Stage 2: user submitting Identifier
openid.CreateRequest(Request.Form["openid_identifier"]).RedirectToProvider();
} else {
// Stage 3: OpenID Provider sending assertion response
switch (openid.Response.Status) {
case AuthenticationStatus.Authenticated:
FormsAuthentication.RedirectFromLoginPage(openid.Response.ClaimedIdentifier, false);
break;
case AuthenticationStatus.Canceled:
ViewData["Message"] = "Canceled at provider";
RenderView("Login");
break;
case AuthenticationStatus.Failed:
ViewData["Message"] = openid.Response.Exception.Message;
RenderView("Login");
break;
}
}
}
What about CardSpace?
OpenID is a spec for a protocol that "eliminates the need for multiple usernames across different websites, simplifying your online experience." What's cool is that it's open, so you (the consumer) gets to pick your Provider. It's not owned by anyone, so it's ours to screw up (or succeed with).
CardSpace is built into Vista and installed on XP when you put .NET 3.0 on your system. There are also Identity Selectors for Safari and Firefox in the works. It's different than OpenID in that it's concerned with strong authentication. Therefore, they are very complimentary.
Here's my CardSpace login as I'm getting ready to log into this blog...
...because my chosen OpenID provider at http://www.myopenid.com (it's free) also supports both InfoCards and SSL Certificates for authentication as well as strong passwords.
Notice the "Sign into Information Card" icon below next to the IconCard purple icon.
An OpenID provider can choose to use anything available with which to authenticate you. Here's a video of a Belgian using an eID to authenticate against an OpenID provider at http://openid.trustbearer.com/ that supports biometric devices, USB keys, and smart cards.
So What?
Get involved and give it a try! Here's some things you can do.
- Sign up for a Free OpenID at MyOpenID or one of the many public OpenID providers out there.
- Then, turn your own domain or your blog into an OpenID.
- Go use your new OpenID at one of the many sites that supports OpenID.
- Come back to this post and leave your first comment using OpenID!
- Watch Simon Willison talk about the case for OpenID (video)
And, if you're a developer, get an OpenID library like dotnetopenid and consider enabling your app. Consider using the Javascript ID Selector to make for a nicer User Experience.
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.
About Newsletter
+1 for MVC and OpenId :)
What do you think about letting the user enter a OpenID and a homepage? Not everyone has delegation setup so linking to their OpenID URL isn't always the best action.
An error has been encountered while processing the page. We have logged the error condition and are working to correct the problem. We apologize for any inconvenience.
Nice work and looking forward to see you at DevTeach!
Scott
Thanks for the tip.
What are your thoughts on using this in place of new account creation? Maybe use this in forum software and just have them enter any additional profile info and have that mapped to their openID?
The idea is that it turns folks off..."create ANOTHER account?" So you offer "Create" and "Login with OpenID" and it's all automatic. You don't even need to store profile details because you can get them from the OpenID provider by requesting them.
I have to admit, though, that Cardspace usage still doesn't make a ton of sense to me (from an end-user's perspective). Like, if I should be storing my cards on my flash drive so that I can use them between computers, etc.
(not that you have to use Cardspace with OpenID)
Again, as you said not that you have to use Cardspace with OpenID.
On another note, it's fun to see my friend Aaron getting the world straightened out about blog comment authentication and things. I too find it very frustrating trying to login or not login, or maybe setup a whole different account to login and comment on blogs. Very frustrating.
For instance, I have previously commented on your blog, so my "details" are pre-populated. I can now put in my openId. Is it going to pull my email from the openId or the E-mail field? If my OpenId profile website field has no value, does it pull the "Home Page" value from the details section?
Anyway, I've been struggling with issues like this on a project that I've been working on. Whenever there is a new paradigm, figuring out the optimal way to go about things is a challenge.
I implemented auto-sign-up using dotnetopenid and I have a method for signing in if you don't have an openId -- but I did some user testing and people were putting in their openId and then additionally filling out the info for the other type of login as well.
Basically, it seems that the lowest friction method for setting up a non openId account is to ask for a username / password, send an opt-in email, and confirm the account when they return to your site via an email link.
OpenId is cool and it is really strong from the perspective of low friction for the initial sign-up process.
Still, it would be a good idea for the community to come up with some sort of consensus of how to integrate OpenId with the flow of a normal "username / password / email confirm" login process since not everyone jumps on the hayride at the same time. You would probably lose a lot of users if you only allowed authentication with OpenId.
Another OpenID area that needs developing is new signups. A lot of places offer OpenID authentication only *after* you've gone through a lenghty signup process when most of that information could have been pre-populated via OpenID. The same even goes for your comment authentication, it seems an AJAX call could be made that shows the Name, EMail and Homepage once the user tabs away from the OpenID box.
So many ideas, so little time :)
* Any volunteers want to write me a JQuery that disables the 3 fields of the OpenID is filled out? and vice versa?
Shawn: Dude, preach on. The big players don't *get it* yet.
Hoping this works,
-- Stu
Cheers,
Timothy
/// <summary>
/// Fired upon login.
/// Note, that straight after login, forms auth will redirect the user to their original page. So this page may never be rendererd.
/// </summary>
protected void OpenIdLogin1_LoggedIn(object sender, OpenIdEventArgs e)
{
e.Cancel = true; //Need to cancel or the control will log us in for free. Eek!
UserToken token = SiteSecurity.Login(e.Response);
if (token != null)
{
SetAuthCookie(token.Name, token.Name);
Response.Redirect(SiteUtilities.GetAdminPageUrl(), true);
}
}
Thank you.
I was trying to setup my dasblog based blog (it's in godaddy) to use openId. I had the blog running with the latest release, but I haven't been able to when using a build supporting open id.
Do you know if the openid capable versions have some issue regarding using medium trust? Any tips on enabling openid?
Thanks!
Comments are closed.