// Calculate SPN (Service Principal Name) using the host name of the request.
// Use the request's 'Host' header if available. Otherwise, use the request uri.
+ // Ignore the 'Host' header if this is proxy authentication since we need to use
+ // the host name of the proxy itself for SPN calculation.
string hostName;
- if (request.HasHeaders && request.Headers.Host != null)
+ if (!isProxyAuth && request.HasHeaders && request.Headers.Host != null)
{
// Use the host name without any normalization.
hostName = request.Headers.Host;
Configuration.Security.ActiveDirectoryUserPassword,
Configuration.Security.ActiveDirectoryName);
+ public static IEnumerable<object[]> EchoServersData()
+ {
+ foreach (Uri serverUri in Configuration.Http.EchoServerList)
+ {
+ yield return new object[] { serverUri };
+ }
+ }
+
+ [MemberData(nameof(EchoServersData))]
+ [ConditionalTheory(nameof(IsDomainJoinedServerAvailable))]
+ public async Task Proxy_DomainJoinedProxyServerUsesKerberos_Success(Uri server)
+ {
+ // We skip the test unless it is running on a Windows client machine. That is because only Windows
+ // automatically registers an SPN for HTTP/<hostname> of the machine. This will enable Kerberos to properly
+ // work with the loopback proxy server.
+ if (!PlatformDetection.IsWindows || !PlatformDetection.IsNotWindowsNanoServer)
+ {
+ throw new SkipTestException("Test can only run on domain joined Windows client machine");
+ }
+
+ var options = new LoopbackProxyServer.Options { AuthenticationSchemes = AuthenticationSchemes.Negotiate };
+ using (LoopbackProxyServer proxyServer = LoopbackProxyServer.Create(options))
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ // Use 'localhost' DNS name for loopback proxy server (instead of IP address) so that the SPN will
+ // get calculated properly to use Kerberos.
+ _output.WriteLine(proxyServer.Uri.AbsoluteUri.ToString());
+ handler.Proxy = new WebProxy("localhost", proxyServer.Uri.Port) { Credentials = DomainCredential };
+
+ using (HttpResponseMessage response = await client.GetAsync(server))
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+
+ int requestCount = proxyServer.Requests.Count;
+
+ // We expect 2 requests to the proxy server. One without the 'Proxy-Authorization' header and
+ // one with the header.
+ Assert.Equal(2, requestCount);
+ Assert.Equal("Negotiate", proxyServer.Requests[requestCount - 1].AuthorizationHeaderValueScheme);
+
+ // Base64 tokens that use SPNEGO protocol start with 'Y'. NTLM tokens start with 'T'.
+ Assert.Equal('Y', proxyServer.Requests[requestCount - 1].AuthorizationHeaderValueToken[0]);
+ }
+ }
+ }
+ }
+
[ConditionalFact(nameof(IsDomainJoinedServerAvailable))]
public async Task Credentials_DomainJoinedServerUsesKerberos_Success()
{