37 Comments

Warning! Don't use this code in production. It is an experimental implementation used for learning purposes only. 

Over the weekend I began looking more deeply into OpenID than I have before. I’ve always been intrigued by OpenID, but the information about it in terms of code samples in .NET have not been impressive. There are a few projects here and there but they all use a library for handling communication with the OpenID providers.

What I wanted was some code that demonstrated how it works. Not a pre-packaged library to code against. No, I wanted to see how the mechanisms worked so I could try it out in real life with my own code. After searching the web for hours, I finally gave up and started to look at the specs so I could write it myself from scratch. The specs are really hard to understand in terms of actual implementation. They say nothing about protocols and formats, which make it pretty hard to grasp.

After a few ours of hitting the wall with the specs in my hand, I finally found this CodePlex project that was abandoned in July 2007. For some reason, this particular project didn’t show up in Google, but I found it by searching the CodePlex site. It made it much easier to understand the mechanisms involved, but it was far from finished and way too complex for what it was trying to solve.

What is OpenID

If you don't know what OpenID is, then take a look at this short video that explains it very well.

The goal

My goal was simple. I wanted to be able to add support for OpenID authentication on existing websites. It means that I wanted to use a standard textbox for entering the OpenID into and a button that would start the whole authentication mechanism. In order to do that, I needed a class that would take care of the communication with the OpenID servers and handle the authentication for me.

So, I ended up with a single small class that can be dumped into any ASP.NET website. It handles the redirection to the OpenID provider and leaves the authentication handling up to the website, but provides all the relevant information for doing so. The class is called OpenID and is very short and simple.

Code example

If you don’t care about the code, but still wants to see how this baby works, check out my OpenID video. You can also download the small code sample at the bottom of the post.

For the following code sample, imaging you have a page called login.aspx. On that page is a textbox called txtOpenId and a button called btnLogon. The user enters his OpenID in the textbox and clicks the button. The event handler of the button’s click event now sends a login request to the OpenID class. The class then redirects the user to her OpenID provider website and is asked to confirm whether or not she will share her information. She accepts and is returned to the login.aspx page.

The login page now retrieves the information given by the OpenID provider from the class. Now the login page can have the information needed to perform the login. Here is the code-behind of the login.aspx.

protected void Page_Load(object sender, EventArgs e)

{

  if (OpenID.IsOpenIdRequest)

  {

    OpenIdData data =  OpenID.Authenticate();

    if (data.IsSuccess)

    {

      StringBuilder sb = new StringBuilder();

      sb.AppendFormat("email: {0}<br />", data.Parameters["email"]);

      sb.AppendFormat("fullname: {0}<br />", data.Parameters["fullname"]);

      sb.AppendFormat("country: {0}<br />", data.Parameters["country"]);

      sb.AppendFormat("phone: {0}<br />", data.Parameters["phone"]);

      sb.AppendFormat("language: {0}<br />", data.Parameters["language"]);

 

      Response.Write(sb.ToString());

    }

  }

 

  btnLogon.Click +=new EventHandler(btnLogon_Click);

}

 

void btnLogon_Click(object sender, EventArgs e)

{

  bool success = OpenID.Login(txtOpenId.Text, "email,fullname,phone", "country,language");

 

  if (!success)

  {

    Response.Write("The OpenID is not valid");

  }

}

I will display the OpenID class, but you can download it here. This example works even on localhost. You don’t need to be on a public IP to interact with OpenID providers.

More information

Download source

Download the code and place the OpenID.cs in the App_Code folder and the login.aspx anywhere in your ASP.NET website. 

Comments

Comment by Troy Goode

Hey Mads,

Nice video. Anything that spreads the word about OpenID and makes it easier for developers to integrate into their applications is a good idea in my book.

Does this mean that you might be investigating this again for integration with BlogEngine? I remember looking into it a while back and seeing that one of the things preventing it's inclusion is the use of Diffie-Hellman encryption by OpenID. That particular encryption pattern is not included in the .Net framework's encryption library and unfortunately is non-trivial to implement without relying on a separate assembly (such as Mono for its BigInteger class).

Troy

Comment by Chris

Way cool... Do you plan on adding this gem to BlogEngine (please?!) as an optional login? Perhaps you could consider getting LiveID working for those of us who would like to embrace the Microsoft platform?

Comment by Chris

A while ago I ran across an article that described the marriage of OpenID and Information Cards as a more secure way to authenticate. After reading this article I immediately went to www.signon.com and created an account. When looking for an OpenID provider I would encourage you to do a little research and find a OpenID provider that supports both OpenID and Information Cards!

Comment by George Chatzimanolis

There is a bug on the video. If you go to the properties of the video you will see that the Title is "User Control Injection" and the comments are "Shows how to inject user controls into blog posts and pages in BlogEngine.NET 1.2" :D

Comment by Troels Thomsen

I'm currently beta testing the first Danish, public OpenID identity provider, LoginBuzz - http://www.loginbuzz.com/ and because of this, I naturally have a large interest in the adaption of OpenID.

Be aware, that your solution is insecure. If you are familiar with the OpenID protocol, you could enter an identity URL into your field, which would store it into the session data. After this you would get redirected to the provider for authentication, but here your knowledge of the protocol comes in handy. A simple HTTP get request towards your server with some specific query string variables, would "prove" your own the OpenID.

Your code lacks the implementation of a "handshake"; that is a shared secret, which is sent straight from the consumer to the identity provider. You have reached your goal of supporting OpenID authentication. But you have also exposed your system wide open, if you use it anywhere. This could be as bad as not sanitizing database inputs though it would require you to use OpenID for something important.

For introducing people to OpenID, the article is fine - just like the code. My point, however, is that no one should use this code for real authentication. Just a friendly warning; I completely agree with you on the lack of proper libraries.

Comment by VirtualTomDavis

Thanks for posting, and I sure it was great. But when I ran it with my new Yahoo open ID, they gave me a message stating that my code (downloaded from here) was outdated. FYI.

VirtualTomDavis

Comment by Andrew Arnott

No offense, Mads... but nonetheless

NO ONE SHOULD USE THIS CODE ON A PRODUCTION SITE!
It can be hacked with a single change of a word in the URL! This library is so simple it skips any signature verification, which means anyone can spoof ANYONE's identity on a site that uses this code. Very dangerous.

Mads, with all due respect, and thanks for promoting OpenId, you should warn your readers before posting code that will likely be reused on others' web sites w/o knowledge of how OpenId really works and end up with security holes all over the web. There's a reason that most OpenId libraries are "big" and yours is small: implementing the spec in a secure way requires a great deal more code than you've got here.

If you or other readers care to look at a more complete and secure implementation for C#, I suggest you check out http://dotnetopenid.googlecode.com.

Comment by neil

Hi Andrew,

it would be much easier to understand your concerns if you could provide a sample for your "It can be hacked with a single change of a word in the URL" claim.

Regards

neil

Comment by Andrew Arnott

Hi Neil,

I'm sure it would be easier, but I intentionally left out the method of the hack because posting exactly how to hack a web site isn't what I wanted to do. I did however send a private message to Mads explaining the method so that he can either correct it or post a warning he found to be appropriate.

Comment by neil

Hi Andrew,

of course, totally agree. Wouldn't be an additional check_authentication helpful against hacking here? I mean the advantage of Mad's solution is, that it can easily be used by .net applications (not asp.net), because most of the C# implementations are designed to be used in asp.net applications only, and it is hard to substitute the lots of "HttpContext.xxx" by other session tracking mechanisms.

Thanks for your comments.


neil

Comment by neil

I'm pretty sure, that enhancing the Authenticate function with a POST to the OpenID provider (check_authentication) would close the vulnerability against hacks, without having to implement the full phalanx of OpenID v.2 (establish association etc.)

Regards

neil

Comment by Andrew Arnott

Neil,

Yes, a check_authentication would protect against the hack. For what it's worth, the DotNetOpenId library does not require ASP.NET, although combining it with ASP.NET allows use of simpler method overloads.

Comment by neil

Hi Andrew,

thanks for confirmation. It's rather simple to implement that check.

Concerning ASP.NET and DotNetOpenId: I meant, DotNetOpenId is full of "HttpContext.whatever" calls. One is not able to use this lib as it is, because this API isn't available outside ASP.NET. Or am I missing something?

Regards

neil

Comment by neil

Hi Troy,

this is nearly what I did too, already. With one exception: The spec talks about, one has to return "exactly" the response received, except the openid.mode, which has of course to be set to "openid.authenticate". Don't you fear to miss something with this selections?


//### get data required for check_authentication
string mode = "check_authentication";
string handle = query["openid.assoc_handle"];
string signature = query["openid.sig"];
string signed = query["openid.signed"];
string extra = string.Empty;

I made good experiences with just reflecting all the stuff I got in the response and just changing the mode.

Regards

neil

Comment by Troy Goode

Hi neil,

I do reflect everything that is requested by teh "openid.signed" field, per the spec. I also pull out the handle, signature, and signed fields prematurely since no matter what is in the signed fields, those fields are required.

I then loop through all fiels requested in "openid.signed" and if they aren't one of the previous pulled fields, I pop them into the "extra" string to later be concatenated together for the POST.

Just to clarify, the spec only requires sending back the response paramaters that were requested by openid.signed. Here is the relevant portion:

"openid.*
Value: The Consumer MUST send all the openid.* response parameters from the openid.signed list which they'd previously gotten back from a checkid_setup or checkid_immediate request, with their values being exactly what were returned from the Provider."

If you still think I'm not doing this correctly, please let me know, I'm open to changing it!

Comment by Troy Goode

Ah, well that would explain the difference in our implementation. I have been working with the 1.1 specification, as specified in the post on my blog.

I will update the code to conform to the 2.0 specification later this weekend. The change should be rather trivial, as I only need to repurpose the current foreach loop to look through the query collection rather than the collection specified by openid.signed.

That said, the 1.1 implementation does appear to work with the OpenID providers I have tried so far...

Comment by Andrew Arnott

Neil,
DotNetOpenId does use HttpContext.Current in several places, but all of those codepaths can be avoided by using the right method overloads. Several web sites are using the library without ASP.NET or HttpContext.Current and doing great.

Comment by neil

DotNetOpenId is not the best sample: Claims to be satisfied with .net 2 (in specification), but requires .net 3 or 3.5 at least (-> Visual Studio 2008 required. For what? Just for some pre-initialized Collections? Hmmm...). Because I'm not already on the 2008 track, I just tried ExtremeSwank, and a quick shot with a ConsoleApp and a

"OpenIDConsumer u = new OpenIDConsumer()" did end up in a NullReferenceException. Guess why? Yes, cause of HttpContext()...

So I tend to use Mads fine and simple lib, because it is rather easy to remove all HttpContext() refs and use a self-made session management. And with the add on of the "openid.authenticate" request it should be safe enough, even for productive environments.

Regards

neil

Comment by Babu Kumarasamy

How to remove the query string send by openID like (openid.assoc_handle, openid.identity and etc.,

protected void Application_BeginRequest(object sender, EventArgs e)
{
httpContext.Response.Redirect("~/add.aspx");
}

I tried like this but I cant able to get the data

Thanks.

Babu Kumarasamy