The Curious Case of the Windows HTTP Proxy

ryanblogpost (1)
The web is the preferred delivery mechanism for most applications these days but there are scenarios where you might want to build a CLI or desktop application for your customers to use. However, once you leave the cozy confines of the browser there are a whole slew of proxy configurations that your poor application will have to deal with if it needs to run within a typical corporate network.
For the purposes of this post, “typical corporate network” means your users are running some flavor of Windows and are sitting behind an authenticating HTTP proxy. While this does seem like a pretty common setup, a surprising number of applications will simply not work in this environment.
Thankfully, when writing a .NET application, the default settings get you most of the way there for free. The default web proxy will automatically use whatever proxy settings the user has configured in IE. If possible, this is what you should rely on. It is tempting to expose proxy hostname and port configuration values that the user can pass to the application, however in some cases a corporate user may not have a single well-known proxy to use. WPAD and PAC files allow proxies to be configured dynamically. See this post for more gory details.
Unfortunately, the default settings do not handle authentication for you out-of-the-box. Web requests will typically fail with a 407 ProxyAuthenticationRequired error. The next step is to examine the Proxy-Authenticate response header returned to see what type of authentication the proxy accepts. Typically this will be some combination of Basic, Digest, NTLM, or Negotiate. If the proxy supports either NTLM or Negotiate, then it is possible to automatically authenticate the signed in user running your application by simply adding the useDefaultCredentials=true attribute to your app.config as described here:

 

This is particularly nice because we don’t have to modify any of our application code nor deal with the headaches of dealing with credential management. Alas, this won’t work if the proxy is configured to use Basic or Digest authentication. While this is an unusual setup, it is something that you will come across in the wild every so often. If this is the case then you will need a way to read in a username and password and then store that in the IWebProxy.Credentials property. As pointed out here, this setup is not typically used because it puts the burden on every application to manage proxy credentials.
In C#, the default proxy settings configured in the app.config are reflected in the WebRequest.DefaultWebProxy static variable. Rather than directly modifying its Credentials, it is cleaner to create a decorator for the proxy that passes through the read requests but manages its own set of credentials without touching the underlying proxy:

public class ProxyWrapper : IWebProxy
{
    private readonly IWebProxy _proxy;
    public ProxyWrapper(IWebProxy proxy)
    {
        _proxy = proxy;
    }
    public ICredentials Credentials
    {
        get; set;
    }
    public Uri GetProxy(Uri destination)
    {
        return _proxy.GetProxy(destination);
    }
    public bool IsBypassed(Uri host)
    {
        return _proxy.IsBypassed(host);
    }
}

Then, you can do something like the following to use the default proxy settings with custom credentials:

// How these are read in depends on the application
string username = ...
SecureString password = ...
IWebProxy proxy = new ProxyWrapper(WebRequest.DefaultWebProxy);
proxy.Credentials = new NetworkCredential(username, password);
WebRequest.DefaultWebProxy = proxy;

This lets you easily switch back to the original credentials that were configured in the app.config or use a different set as needed.
Note that while all of this is pretty straightforward for people using .NET, it might not be as easy to support authenticating proxies (particularly ones that only use NTLM and Negotiate) in http libraries used in other languages. In these scenarios, some people have had success using cntlm as a proxy for the authenticating proxy.
TL;DR: For people writing applications in .NET, you should simply set useDefaultCredentials=true in your app.config file and that should “just work” most of the time.

Similar Posts