OpenID Check_Authentication In C#
// April 11th, 2008 // 12 Comments » // 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.



