OpenID Check_Authentication In C#

// April 11th, 2008 // C#

Earlier this year Mads Krisensen (of BlogEngine.net fame) posted a lightweight implementation of OpenID using C#. In the comments on Mads’ post, Andrew Arnott (a developer of the DotNetOpenId library) mentioned that the example Mads had posted could "be hacked with a single change of a word in the URL." This is what is technically referred to as a Very Bad Thing. Andrew and another poster named "neil" went on to elaborate that implementing OpenID’s "check_authentication" algorithm would close this security hole. Unfortunately as of the writing of this article neither Mads nor any of the commentors have provided an implementation of check_authentication that works with the class Mads posted (bear in mind that Andrew only brought up this issue less than a week ago, so Mads may very well be working on it).

Fast forward to yesterday when I was researching my options for implementing OpenID for the next release of the ASP.Net MVC Membership Starter Kit. I liked Mads solution more than the other OpenID libraries that are current available because of its brevity and how easy it is to include it in a project without introducing an extra assembly dependency, so I decided to go ahead and add the check_authentication functionality. A quick read of that portion of the OpenID spec and a couple hours of coding/testing and I think I’m about finished.

Here is the method you need to add to Mads’ class:

   1: private static bool CheckAuthentication( NameValueCollection query )
   2: {
   3:  
   4:     //### get data required for check_authentication
   5:     string mode = "check_authentication";
   6:     string handle = query["openid.assoc_handle"];
   7:     string signature = query["openid.sig"];
   8:     string signed = query["openid.signed"];
   9:     string extra = string.Empty;
  10:  
  11:     //### loop through fields required by "openid.signed" and retrieve that data
  12:     if( !string.IsNullOrEmpty(signed) )
  13:     {
  14:         string[] exemptions = { "mode", "assoc_handle", "sig", "signed" };
  15:         string[] fields = signed.Split(',');
  16:         foreach( string field in fields )
  17:         {
  18:             if( exemptions.Contains(field) ) continue;
  19:             extra += string.Format( "openid.{0}={1}&", field, HttpUtility.UrlEncode( query[ "openid." + field ] ) );
  20:         }
  21:         extra = "&" + extra.Substring( 0, extra.Length - 1 );
  22:     }
  23:  
  24:     //### combine all the data together to form the request
  25:     string post = string.Format( "openid.mode={0}&openid.assoc_handle={1}&openid.sig={2}&openid.signed={3}{4}",
  26:         mode,
  27:         HttpUtility.UrlEncode( handle ),
  28:         HttpUtility.UrlEncode( signature ),
  29:         HttpUtility.UrlEncode( signed ),
  30:         extra
  31:     );
  32:  
  33:     //### begin sending request
  34:     HttpWebRequest request = (HttpWebRequest)WebRequest.Create( query["openid.op_endpoint"] );
  35:     request.Method = "POST";
  36:     request.ContentType = "application/x-www-form-urlencoded";
  37:     request.ContentLength = post.Length;
  38:  
  39:     //### transmit POST data
  40:     using( StreamWriter sw = new StreamWriter(request.GetRequestStream()) )
  41:         sw.Write(post);
  42:  
  43:     //### get response
  44:     string html = "";
  45:     using( HttpWebResponse response = (HttpWebResponse)request.GetResponse() )
  46:         using( StreamReader sr = new StreamReader( response.GetResponseStream() ) )
  47:             html = sr.ReadToEnd();
  48:  
  49:     //### determine if check_authentication passed or not
  50:     if( string.IsNullOrEmpty(html) || !html.StartsWith( "is_valid:" ) || html.StartsWith( "is_valid:false" ) )
  51:         return false;
  52:     else if( html.StartsWith( "is_valid:true" ) )
  53:         return true;
  54:     else
  55:         throw new InvalidOperationException( "Unexpected return from OpenID check_authentication." );
  56:  
  57: }

Now you need to make sure that method is called from somewhere. I chose to make the method private and just call it from inside the Authenticate method. Within the Authenticate method replace:

   1: // Make sure the incoming request's identity matches the one stored in session
   2: if( query["openid.claimed_id"] != data.Identity )
   3:     return data;

… with…

   1: // Make sure the incoming request's identity matches the one stored in session
   2: if( query["openid.claimed_id"] != data.Identity )
   3:     return data;
   4: else if( !CheckAuthentication( query ) )
   5:     throw new UnauthorizedAccessException( "OpenID False Verification Detected" );

And that’s it!

If anyone with more experience than I in OpenID waters sees a problem with my implementation, let me know and I will try to get it fixed quickly.

NOTE: I am aware that Mads’ implementation and the use of ‘check_authentication’ is considered an overly chatty use of OpenID. It seems to me that the extra complexity required to implement OpenID 2.0 protocol is just not worthwhile for most OpenID consumers. Feel free to let me know why this is a stupid position to take.

Kick It on DotNetKicks.comShout It on DotNetShoutOuts.com

12 Responses to “OpenID Check_Authentication In C#”

  1. Nicolas Cadilhac says:

    Hi Troy,

    error CS1061: ‘System.Array’ does not contain a definition for ‘Contains’ and no extension method ‘Contains’ accepting a first argument of type ‘System.Array’ could be found (are you missing a using directive or an assembly reference?)

    This happens at line 18.

  2. Troy Goode says:

    Hi Nicolas,

    You have to add “using System.Linq;” to the class file to give the string array the .Contains method.

  3. Nicolas Cadilhac says:

    oops… thx

  4. Troy Goode says:

    No problem, I probably should’ve been more explicit about that since Mads’ version didn’t import that namespace, but honestly I just forgot.

    Hopefully anyone else that runs across this issue will look in the comments, because I’m too lazy to update the post. ;-)

  5. This is really cool. Thanks for improving it. Now I might even consider using it myself :)

  6. Troy Goode says:

    Please do! You might want to wait a day or so, “neil” notified me via a comment on your blog that my implementation is slightly flawed (based off the 1.1 spec instead of 2.0).

    It shouldn’t take me long to fix it, so hopefully I can have it done tonight. Sunday night at the latest.

    Thanks for doing the hard work of getting a small, clean OpenID consumer up and running. I’ve integrated it into my Mvc Membership Starter Kit project and am using it successfully in another project that I’m working on. It’s been really cool! =)

    I’ll leave a comment here when I’ve posted the updated version of this method.

  7. [b]Security hole[/b]
    Hi Troy,
    This is a good addition to Mads’ implementation, but unfortunately this also has a security hole that would allow me to log in as anyone. Email me and I’ll explain what you need to do to plug it. (don’t want to make it public, you know).

    It’s difficult to implement only part of a spec and get it securely done. Different pieces of the spec that add some complexity turn out to be necessary to ensure security against identity theft. And the closer you get to a secure implementation, the closer you get to something worth reusing… oops, now you have a library. :)

  8. Samuli Lintunen says:

    Troy, Andrew and everybody involved with the membership starter kit and OpenID implementations,
    Good job!

    There are two active, comprehensive ASP.NET OpenID implementations around:
    http://extremeswank.com/aspnet_openid.html
    and Andrew’s
    http://code.google.com/p/dotnetopenid/

    I currently have the ExtremeSwank one in use. Andrew, have you studied this one? If yes, do you want to comment on its implementation vs yours (sales speech :-) )?

    Troy and Andrew, how’s the OpenID security issue, Andrew mentioned above, progressing with the Starter Kit?

    Troy, would it be easy to plug-in Andrew’s OpenID library (or the ExtremeSwank one) to the Starter Kit?

  9. Troy Goode says:

    Hi Samuli,

    Rather than continue to fix the code Mads provided, I am currently working on removing the current OpenID code from the starter kit and will be replacing it with one of the two implementations above. Currently I am leaning more toward Andrew’s, but since I don’t know of any direct comparisons between the two projects, so either could wind up being the one I choose (I am starting with Andrew’s mainly because [1] he brought these issues to my attention and [2] there are some hefty names behind his project).

    The Codeplex work item associated with this task (in case you want to track it’s progress) is:
    http://www.codeplex.com/MvcMembership/WorkItem/View.aspx?WorkItemId=544

    I had been hoping to finish the work last night, but unfortunately my attention has been diverted elsewhere for the moment. I’ll have it finished as soon as I can.

  10. Samuli Lintunen says:

    Cool. I’ll be checking out the progress. Keep up the great work!

  11. Hi Troy,
    I’m pleased with your decision to use a library, whichever one you choose.

    Troy/Samuli,
    In response to Samuli’s request for a sales pitch, I’ve posted it on my blog:
    http://blog.nerdbank.net/2008/04/why-dotnetopenid-as-your-c-openid.html

  12. Andre says:

    Nice implementation – I totally agree, that this light implementation is complex enough.