Monday, August 16, 2010

Automatic Authentication with the Request Builder

The Request Builder feature in recent versions of Fiddler includes a number of enhancements, including the ability to follow HTTP redirections, and to automatically authenticate (using the current user's credentials) to servers that demand authentication using the NTLM or Negotiate (NTLM/Negotiate) challenge-response protocols. Following redirections is simple enough, but properly constructing a response to a server's Negotiate authentication request entails some very complicated code. The problem is that while .NET can automatically add credentials to HTTPWebRequest objects, there's no trivial way to calculate the challenge-response string when you're using a socket directly (as Fiddler does).
Fortunately, a (now-defunct) blog post from "Jeff" suggested a hack that works rather well—use Reflection to grab internal fields (including the challenge-response string) from a dummy WebRequest object. While using Reflection to grab internal fields is inherently fragile (e.g. subject to breakage in updated versions of the framework) this particular trick appears to work reliably in both .NETv2 and .NETv4 frameworks.
Each Fiddler session keeps a dummy WebRequest variable which is not initialized by default.
private WebRequest __WebRequestForAuth = null;
If, however, the server returns a WWW-Authenticate header demanding NTLM or Negotiate, and the Session Flag x-Builder-AutoAuth is present on the session, then the object will be initialized, with the current session's URL:
if (null == __WebRequestForAuth)
{  

__WebRequestForAuth = HttpWebRequest.Create(this.fullUrl);
}
Then, we use Reflection to get the Authorization String we'll be adding to the request
try              
{                  
#region Use-Reflection-Magic-To-Tweak-Internals                    
Type tWebReq = __WebRequestForAuth.GetType();
tWebReq.InvokeMember("Async", BindingFlags.Default | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.SetProperty, null, __WebRequestForAuth, new object[] { false });
object objServerAuthState = tWebReq.InvokeMember("ServerAuthenticationState", BindingFlags.Default | BindingFlags.NonPublic |
BindingFlags.Instance | BindingFlags.GetProperty, null, __WebRequestForAuth, new object[0]);                    
   
if (objServerAuthState == null) throw new ApplicationException("Auth state is null");
Type tAuthState = objServerAuthState.GetType();
tAuthState.InvokeMember("ChallengedUri", BindingFlags.Default | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetField, null, objServerAuthState, new object[] { new Uri(this.fullUrl)});