locked
PreAuthenticate, 401's, Integrated windows authentication

    Question

  • In a .NET 2.0 web-service on IIS 6.0, does setting the client proxy's PreAuthenticate property to true when using Integrated security (Windows authentication) remove the 401 errors that cause multiple round trips ?

     

    I've saw .NET 1.1 articles that said PreAuthenticate doesnt work with windows authentication:

    http://www.pluralsight.com/blogs/craig/archive/2004/03/17/1255.aspx

     

    but haven't found anything to say its fixed in .NET 2.0

     

     

    Thursday, October 25, 2007 2:06 PM

Answers

  • Hi Andrew, thanks for your post, it gives me an opportunity to, hopefully, set some things straight on the topic. :)

    First I'll answer your question: in .NET 1.1 SP1 and .NET 2.0 we've added support for PreAuthenticate to all available HTTP based authentication protocols. Just to be sure you can run the following small app:

    namespace Test

    {

        using System;

        using System.Collections;

        using System.Net;

     

        class Program

        {

            static void Main(string[] args)

            {

                IEnumerator enumerator = AuthenticationManager.RegisteredModules;

                while (enumerator.MoveNext())

                {

                    IAuthenticationModule module = (IAuthenticationModule)enumerator.Current;

                    Console.WriteLine("AuthenticationType: " + module.AuthenticationType + ", CanPreAuthenticate: " + module.CanPreAuthenticate);

                }

            }

        }

    }

     

    You should see the following output which confirms my assertion above:

    AuthenticationType: Negotiate, CanPreAuthenticate: True

    AuthenticationType: Kerberos, CanPreAuthenticate: True

    AuthenticationType: NTLM, CanPreAuthenticate: True

    AuthenticationType: Digest, CanPreAuthenticate: True

    AuthenticationType: Basic, CanPreAuthenticate: True

     

    I've found that many folks are confused by how the PreAutenthicate setting affects the behavior of the HTTP stack, so first let me add a pointer to the official documentation as it does a reasonable job at describing that:
    http://msdn2.microsoft.com/en-us/library/system.net.httpwebrequest.preauthenticate.aspx

    What people typically don't give enough attention to is the following sentence in the remarks: "After a client request to a specific Uri is successfully authenticated". This means that the 1st time you send an HTTP request to a specific Uri, it is intentional that the request won't have an Authorization header. After the first request is successfully authenticated (and yes, this will involve one [or more] 401 response[s] on the wire - I will explain the plural later (*)), subsequent requests will avoid that first 401 response. The reason why it's best to wait for the first request to be successfully authenticated is simple: the server's first 401 response will contain the list of authentication protocols it supports, this allows the client to evaluate ahead of time whether it has any credentials that are compatible with the list.

    Some old versions of the Apache HTTP server don’t play well with this behavior and sends a 500 response if a client doesn't include credentials on the very first request to the server. This behavior in my opinion violates the guidance from RFCs 2616 and 2617 which suggests that a server should respond with a 401 to a request that has no Authorization header. Many folks have posted workarounds to this behavior that works with Basic by explicitly creating and adding the Authorization header to a request.

    If those don't work for you, another option is to use the following hack: the EnablePreAuthentication() method below takes a request Uri and a string indicating the authentication protocol you wish to use to access that Uri and makes a call into a private method in AuthenticationManager that fools it into thinking that  a client request to the specific Uri was successfully authenticated using that authentication protocol. Keep in mind that this is a hack, it uses reflection to call a private method, it will work in current versions but there's no guarantee that it will keep working in the future! Also note that this won't work for Digest as this protocol always requires a first roundtrip to the server to establish a valid nonce.

    namespace Test

    {

        using System;

        using System.Collections;

        using System.Net;

        using System.Reflection;

     

        class Program

        {

            static void Main(string[] args)

            {

                Uri uri = new Uri("http://www.msn.com");

                EnablePreAuthentication(uri, "basic");

                WebRequest webRequest = WebRequest.Create(uri) as HttpWebRequest;

                webRequest.Credentials = new NetworkCredential("username", "password");

                webRequest.PreAuthenticate = true;

                webRequest.GetResponse();

            }

            static bool EnablePreAuthentication(Uri uri, string authenticationType)

            {

                IEnumerator e = AuthenticationManager.RegisteredModules;

                while (e.MoveNext())

                {

                    IAuthenticationModule module = e.Current as IAuthenticationModule;

                    if (string.Compare(module.AuthenticationType, authenticationType, true) == 0)

                    {

                        MethodInfo mi = typeof(AuthenticationManager).GetMethod("BindModule", BindingFlags.NonPublic | BindingFlags.Static);

                        mi.Invoke(null, new object[] { uri, new Authorization(null), module });

                        return true;

                    }

                }

                return false;

            }

        }

    }

     

    (*) Let me explain why I previously talked about more than one 401 responses. For NTLM, there are typically two 401 responses before you can build a valid token for the server will accept (this can happen for Kerberos as well, for example if there is a clock skew to correct, but it's not typical), so even if you set PreAutenthicate to true you will still see a 401 response on the wire and the request will have to be sent a second time. When Integrated Windows Authentication is used, the authentication protocol is negotiated on the fly (you will see the string "WWW-Authenticate: Negotiate "), and that's why you may or may not see a 401 depending on what protocol you're using. In order to eliminate these additional 401 responses one can use the UnsafeAuthenticatedConnectionSharing property and set it to true (it's false by default):
    http://msdn2.microsoft.com/en-us/library/system.net.httpwebrequest.unsafeauthenticatedconnectionsharing.aspx

    This was introduced the leverage the behavior available in IIS known as AuthPersistence, whereby all HTTP requests sent on a TCP connection where a request was successfully authenticated, are associated with the same security context:
    http://msdn2.microsoft.com/en-us/library/ms525244.aspx

    Note how, in order for this to work the way I described it, you will need the server's configuration to have this enabled for the authentication protocol you intend to use. Because of its popularity in IIS and performance characterstics, this behavior was also added to the HttpListener:
    http://msdn2.microsoft.com/en-us/library/system.net.httplistener.unsafeconnectionntlmauthentication(VS.80).aspx

    Hopefully this explains how things work and provides some helpful information and code samples.
    If not please provide feedback and I'll make edits to this post until it does. :)

    Tuesday, October 30, 2007 2:13 AM
    Moderator

All replies

  • Hi Andrew, thanks for your post, it gives me an opportunity to, hopefully, set some things straight on the topic. :)

    First I'll answer your question: in .NET 1.1 SP1 and .NET 2.0 we've added support for PreAuthenticate to all available HTTP based authentication protocols. Just to be sure you can run the following small app:

    namespace Test

    {

        using System;

        using System.Collections;

        using System.Net;

     

        class Program

        {

            static void Main(string[] args)

            {

                IEnumerator enumerator = AuthenticationManager.RegisteredModules;

                while (enumerator.MoveNext())

                {

                    IAuthenticationModule module = (IAuthenticationModule)enumerator.Current;

                    Console.WriteLine("AuthenticationType: " + module.AuthenticationType + ", CanPreAuthenticate: " + module.CanPreAuthenticate);

                }

            }

        }

    }

     

    You should see the following output which confirms my assertion above:

    AuthenticationType: Negotiate, CanPreAuthenticate: True

    AuthenticationType: Kerberos, CanPreAuthenticate: True

    AuthenticationType: NTLM, CanPreAuthenticate: True

    AuthenticationType: Digest, CanPreAuthenticate: True

    AuthenticationType: Basic, CanPreAuthenticate: True

     

    I've found that many folks are confused by how the PreAutenthicate setting affects the behavior of the HTTP stack, so first let me add a pointer to the official documentation as it does a reasonable job at describing that:
    http://msdn2.microsoft.com/en-us/library/system.net.httpwebrequest.preauthenticate.aspx

    What people typically don't give enough attention to is the following sentence in the remarks: "After a client request to a specific Uri is successfully authenticated". This means that the 1st time you send an HTTP request to a specific Uri, it is intentional that the request won't have an Authorization header. After the first request is successfully authenticated (and yes, this will involve one [or more] 401 response[s] on the wire - I will explain the plural later (*)), subsequent requests will avoid that first 401 response. The reason why it's best to wait for the first request to be successfully authenticated is simple: the server's first 401 response will contain the list of authentication protocols it supports, this allows the client to evaluate ahead of time whether it has any credentials that are compatible with the list.

    Some old versions of the Apache HTTP server don’t play well with this behavior and sends a 500 response if a client doesn't include credentials on the very first request to the server. This behavior in my opinion violates the guidance from RFCs 2616 and 2617 which suggests that a server should respond with a 401 to a request that has no Authorization header. Many folks have posted workarounds to this behavior that works with Basic by explicitly creating and adding the Authorization header to a request.

    If those don't work for you, another option is to use the following hack: the EnablePreAuthentication() method below takes a request Uri and a string indicating the authentication protocol you wish to use to access that Uri and makes a call into a private method in AuthenticationManager that fools it into thinking that  a client request to the specific Uri was successfully authenticated using that authentication protocol. Keep in mind that this is a hack, it uses reflection to call a private method, it will work in current versions but there's no guarantee that it will keep working in the future! Also note that this won't work for Digest as this protocol always requires a first roundtrip to the server to establish a valid nonce.

    namespace Test

    {

        using System;

        using System.Collections;

        using System.Net;

        using System.Reflection;

     

        class Program

        {

            static void Main(string[] args)

            {

                Uri uri = new Uri("http://www.msn.com");

                EnablePreAuthentication(uri, "basic");

                WebRequest webRequest = WebRequest.Create(uri) as HttpWebRequest;

                webRequest.Credentials = new NetworkCredential("username", "password");

                webRequest.PreAuthenticate = true;

                webRequest.GetResponse();

            }

            static bool EnablePreAuthentication(Uri uri, string authenticationType)

            {

                IEnumerator e = AuthenticationManager.RegisteredModules;

                while (e.MoveNext())

                {

                    IAuthenticationModule module = e.Current as IAuthenticationModule;

                    if (string.Compare(module.AuthenticationType, authenticationType, true) == 0)

                    {

                        MethodInfo mi = typeof(AuthenticationManager).GetMethod("BindModule", BindingFlags.NonPublic | BindingFlags.Static);

                        mi.Invoke(null, new object[] { uri, new Authorization(null), module });

                        return true;

                    }

                }

                return false;

            }

        }

    }

     

    (*) Let me explain why I previously talked about more than one 401 responses. For NTLM, there are typically two 401 responses before you can build a valid token for the server will accept (this can happen for Kerberos as well, for example if there is a clock skew to correct, but it's not typical), so even if you set PreAutenthicate to true you will still see a 401 response on the wire and the request will have to be sent a second time. When Integrated Windows Authentication is used, the authentication protocol is negotiated on the fly (you will see the string "WWW-Authenticate: Negotiate "), and that's why you may or may not see a 401 depending on what protocol you're using. In order to eliminate these additional 401 responses one can use the UnsafeAuthenticatedConnectionSharing property and set it to true (it's false by default):
    http://msdn2.microsoft.com/en-us/library/system.net.httpwebrequest.unsafeauthenticatedconnectionsharing.aspx

    This was introduced the leverage the behavior available in IIS known as AuthPersistence, whereby all HTTP requests sent on a TCP connection where a request was successfully authenticated, are associated with the same security context:
    http://msdn2.microsoft.com/en-us/library/ms525244.aspx

    Note how, in order for this to work the way I described it, you will need the server's configuration to have this enabled for the authentication protocol you intend to use. Because of its popularity in IIS and performance characterstics, this behavior was also added to the HttpListener:
    http://msdn2.microsoft.com/en-us/library/system.net.httplistener.unsafeconnectionntlmauthentication(VS.80).aspx

    Hopefully this explains how things work and provides some helpful information and code samples.
    If not please provide feedback and I'll make edits to this post until it does. :)

    Tuesday, October 30, 2007 2:13 AM
    Moderator
  • Now that's what I call an answer!

     

    Thanks.
    Wednesday, October 31, 2007 2:49 PM
  • I'm glad to hear that, please mark my post helpful so others are encouraged to read it .

    Wednesday, October 31, 2007 3:37 PM
    Moderator
  • As far as I see from this article:

    http://support.microsoft.com/kb/908573

    different settings are required for HttpWebRequest to upload large files in NTLM and Kerberos authentication modes. For NTLM HttpWebRequest.PreAuthenticate must be set to false, for Kerberos HttpWebRequest.PreAuthenticate must be set to true.

    How do I detect on a client side if NTLM or Kerberos was chosen?
    Friday, June 26, 2009 5:15 PM
  • Thanks for the detailed information ranamauro. It's very helpful.
    Friday, October 15, 2010 5:00 PM