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)});                    
#endregion Use-Reflection-Magic-To-Tweak-Internals

    string sAuthString = (bIsProxyAuth) ? oResponse["Proxy-Authenticate"] : oResponse["WWW-Authenticate"];

    // Next line uses default creds for current user. You could manually specify like so = new NetworkCredential("user", "pass", "domain") :
    ICredentials oCreds = CredentialCache.DefaultCredentials;  // This may throw, due to clock skew, etc.                    
    Authorization auth = AuthenticationManager.Authenticate(sAuthString, __WebRequestForAuth, oCreds);
    if (null == auth) throw new Exception("AuthenticationManager.Authenticate returned null.");                     
    string sAuth = auth.Message;

    // Create the new session that we'll resend with credentials
    nextSession = new Session(oRequest.pipeClient, oResponse.pipeServer);
    if (!auth.Complete)
    {
        nextSession.__WebRequestForAuth = __WebRequestForAuth;
    }
    __WebRequestForAuth = null;

    // Set the headers and body for the new session, using the 
    // Challenge-Response that we Reflected out of the Framework
    nextSession.requestBodyBytes = this.requestBodyBytes;
    nextSession.oRequest.headers = (HTTPRequestHeaders)this.oRequest.headers.Clone();
    nextSession.oRequest.headers[bIsProxyAuth ? "Proxy-Authorization" : "Authorization"] = sAuth;
catch (Exception eX) { FiddlerApplication.DoNotifyUser("Automatic authentication was unsuccessful.\n\n" + eX.Message, "Automatic Authentication failed"); }

That’s all there is to it.

-Eric

Comments (19)

  1. Steve Humphreys says:

    Eric

    Would it be possible to create a fiddler rule that authenticate the requests with a different user credentials using this technique.  

  2. EricLaw [MSFT] says:

    @Steve: That is possible in Fiddler 2.3.0.2+. (http://www.fiddler2.com/…/fiddler2betasetup.exe)

    Inside the OnBeforeRequest handler, add

      oSession.oFlags["x-AutoAuth"] = "domain\user:password";

    If specifying a domain, be sure to use a double-backslash instead of a single backslash.

  3. Steve Humphreys says:

    Eric

    Very cool, works great.  Is this also in the Fiddler Core?

    Steve

  4. Steve Humphreys says:

    Eric

    Here is what I'm trying to do.

    C# Selenium Client

      Add User Information Header

      Ask for Page

    Selenium RC (Use Fiddler as a proxy)

     (Add user infomation in request header)

    Fiddler

     (Read user infomation headers and add oSession.oFlags["x-AutoAuth"] = "domain\user:password";)

    Web Server

      (Should be autheincated, Windows Authenication)

    This works with the Fiddler.exe but I need to run on a server in the backgroung so I was hoping to create a windows service that hosts the FiddlerCore.

    Thanks

    Steve

  5. EricLaw [MSFT] says:

    It's not currently in FiddlerCore but will be in the next update.

  6. Steve Humphreys says:

    Any idea when the next FiddlerCore update might be?

  7. Steve Humphreys says:

    Eric

    Thanks for the update.

    Steve

  8. Steve Humphreys says:

    Eric

    I'm testing the new FiddlerCore and getting the following error message:

    ** LogString: Automatic authentication was unsuccessful. The function requested is not supported.

    Is there anything special I need to do in my project setup.  I'm using the Demo project and adding the

    oSession.oFlags["x-AutoAuth"] = "domain\user:password";

    in the BeforeRequest delegate.

  9. EricLaw [MSFT] says:

    @Steve: That's the error you'll receive (preceded by two or three HTTP/401 responses) if the provided credentials are incorrect.

  10. Steve Humphreys says:

    Eric

    I must be doing something wrong.  I'm using the same auth string in the custom rules for Fiddler as I'm using in the Fiddler Core and Fiddler works great.  I'm using Visual Studio 2008 to compile the FiddlerCore demo application using 2.0 as the target framework.

    Any ideas?

    Thanks

    Steve

  11. EricLaw [MSFT] says:

    Are you seeing the preceding 401 responses?

    Please send me your sample FiddlerCore code. Is your FiddlerCore instance running in a service account or anything of that nature? You haven't specified a leading @ sign before the authentication string in your C# code, have you?

    E.g. you can use:

    oSession.oFlags["x-AutoAuth"] = "domain\user:password";

    or

    oSession.oFlags["x-AutoAuth"] = @"domainuser:password";

    but not

    oSession.oFlags["x-AutoAuth"] = @"domain\user:password";  <– creates two backslashes

  12. Steve Humphreys says:

    Eric

    I have compared Fiddler to Fiddler core responses, both give 401, 401, then a 302.  

    After the 302 in Fiddler the application comes up, but after the 302 is FiddlerCore IE pops up the dialog box for credientials.  If I hit cancel on te dialog box I get unauthorized error from server, then if I refresh the site comes up fine.

    I'm wondering if I'm missing a setting in FiddlerCore to make it behave like Fiddler?

    Thoughts.

    Steve

  13. Steve Humphreys says:

    Eric

    Thanks for all you help.  I figured out the issue.

    Need to make sure the response is buffered.

    oS.bBufferResponse = true;

    Steve

  14. EricLaw [MSFT] says:

    Thanks, Steve. That's actually an interesting bug in FiddlerCore; that shouldn't be required. Until it's fixed, your best bet is to attach an event handler to ResponseHeadersAvailable, and put this code inside the implementation:

    if ((oS.responseCode == 401) && oS.oFlags.ContainsKey("x-AutoAuth"))
    {
       oS.bBufferResponse = true;
    }

  15. Steve Humphreys says:

    Eric

    I have a solution working for HTTP requests but when I try HTTPS the auto auth flag seems to be ignored.

    Any ideas.

    Thanks

    Steve

  16. EricLaw [MSFT] says:

    Steve, rather than trying to do this in blog comments, please use the Fiddler discussion group or email me directly.

    Additionally, it'll be about 1000% easier to troubleshoot this if you send me a .SAZ file with the traffic capture.

    thx.

  17. Note: The above code sample may be incomplete for Kerberos. You may need to set the ChallengedSpn field in order for Negotiate/Kerberos to succeed.

  18. András Székely says:

    Hi Everyone,

    Can somebody send me the partially or full code to have this authenticathion issue. The above code part is only part and i do not have now the time to figure it out. This is exactly my problem, and it would be really great !

    Thanks at all!

    szandars2524@gmail.com