OpenID Check_Authentication In C#

April 11, 2008 12:01 AM

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.

Tags: ,
Categories: C#
Actions: E-mail | Permalink | Comments (12) RSS Feed for this post's comments.

Comments

4/11/2008 1:29 PM #

Nicolas Cadilhac

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.

Nicolas Cadilhac ca

4/11/2008 1:38 PM #

Troy Goode

Hi Nicolas,

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

Troy Goode us

4/11/2008 1:39 PM #

Nicolas Cadilhac

oops... thx

Nicolas Cadilhac ca

4/11/2008 1:48 PM #

Troy Goode

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. Wink

Troy Goode us

4/11/2008 6:49 PM #

Mads Kristensen

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

Mads Kristensen dk

4/11/2008 8:26 PM #

Troy Goode

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.

Troy Goode us

4/12/2008 1:37 AM #

Andrew Arnott

Security hole
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. Smile

Andrew Arnott us

4/15/2008 8:17 PM #

Samuli Lintunen

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 Smile)?

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?

Samuli Lintunen fi

4/15/2008 8:46 PM #

Troy Goode

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:
www.codeplex.com/.../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.

Troy Goode us

4/16/2008 12:19 PM #

Samuli Lintunen

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

Samuli Lintunen fi

4/16/2008 12:35 PM #

Andrew Arnott

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:
blog.nerdbank.net/.../...nid-as-your-c-openid.html

Andrew Arnott us

5/19/2008 10:29 AM #

Andre

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

Andre de

Add Comment


(Will show your Gravatar icon)  

  Country flag

biuquote
  • Comment
  • Preview
Loading




Troy Goode

Troy Goode
Microsoft Certified Professional Developer
AddThis Feed Button

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in  anyway.

© Copyright 2008

Colophon

Powered by:
BlogEngine.NET 1.4
Template:
Designs by Darren
Header Font:
Stamper
Syntax Highlighting:
WLW Code Snippet Plugin