400 bad request when POSTing WebService or WCF request from IE

Symptom

 

Let’s suppose such a scenario:

 

Ø You have a website which enables NTLM authentication.

Ø There’s a virtual directory under this website, which enables Anonymous authentication.

Ø There’s a Web Service or WCF Service in this virtual directory.

Ø You have a web page in the root directory of the website, and you try to post a request to the Web/WCF Service through AJAX/Silverlight.

You will possibly get “400 bad request” error for this Web Service or WCF Service call. In some situation, it also happens if you just put the Web/WCF service in the same web folder as the web page but only enable Anonymous authentication for Web/WCF service.

 

Root Cause

 

The below items can explain why the problem happens.

        A. NTLM is a Challenge/Response protocol, and the authentication procedure is as below. 

       

a. Typically, the client issues an initial anonymous request. When the anonymous request is rejected, IIS returns a 401.2 error and the WWW-Authenticate headers.

 

b. If the client fails or does not support Kerberos, the Negotiate and NTLM header values initiate an NTCR authentication exchange. The client closes the TCP connection, opens a new one, and sends a request that includes an Authorization: NTLM header. This header also includes encoded text that represents the users UserName, ComputerName, and Domain. This text is used by the Windows Security Support Provider Interface (SSPI) to generate the challenge. If the user account is not a local Windows account on the IIS server, the data is passed on to an appropriate domain controller, which then generates the challenge.

 

c. The challenge is sent to the client and IIS returns another 401.2 error.

 

d. The client uses its password and the challenge to create a mathematical hash. The client sends the hash back to the server in another Authorization: NTLM header.

 

e. The server accepts the response, and the local security provider or the appropriate domain controller recreates the same hash and compares the two. If they match, the user is successfully authenticated.

 

For more information, please refer to https://www.microsoft.com/technet/prodtechnol/WindowsServer2003/Library/IIS/8feeaa51-c634-4de3-bfdc-e922d195a45e.mspx

 

B. For IE, it assumes that the subsequent requests to Web/WCF Service also require NTLM authentication. So, it is expecting to get a challenge and resubmit the request. In this situation, IE just suppresses the request body and sets content-length to 0. However, the server side Web/WCF service only need “Anonymous” authentication and it responds 400 instead of 401 because of the “content-length=0”.

  

Analysis

 If you capture a Network trace with Network Monitor, you will get the below packets.

 

2 10.172.26.63 10.172.8.69 HTTP HTTP:Request, GET /ActiveXTest/ajaxClient2.htm

3 10.172.8.69 10.172.26.63 HTTP HTTP:Response, HTTP/1.1, Status: Unauthorized, URL: /ActiveXTest/ajaxClient2.htm Using Multiple…

4 10.172.26.63 10.172.8.69 HTTP HTTP:Request, GET /ActiveXTest/ajaxClient2.htm , Using NTLM Authorization

5 10.172.8.69 10.172.26.63 HTTP HTTP:Response, HTTP/1.1, Status: Unauthorized, URL: /ActiveXTest/ajaxClient2.htm

6 10.172.26.63 10.172.8.69 HTTP HTTP:Request, GET /ActiveXTest/ajaxClient2.htm , Using NTLM Authorization

7 10.172.8.69 10.172.26.63 HTTP HTTP:Response, HTTP/1.1, Status: Ok, URL: /ActiveXTest/ajaxClient2.htm

8 10.172.26.63 10.172.8.69 HTTP HTTP:Request, POST /ActiveXTest/Anonymous/WebService.asmx , Using NTLM Authorization

9 10.172.8.69 10.172.26.63 HTTP HTTP:Response, HTTP/1.1, Status: Bad request, URL: /ActiveXTest/Anonymous/WebService.asmx

 

As you see, it still uses NTLM to post the request but gets “Bad request” error. If you check the details of the POST request, you will find “content-length=0” and NTLM authorization as below.

 

  Frame: Number = 8, Captured Frame Length = 694, MediaType = ETHERNET

+ Ethernet: Etype = Internet IP (IPv4),DestinationAddress:[00-00-0C-07-AC-09],SourceAddress:[00-15-5D-09-2B-04]

+ Ipv4: Src = 10.172.26.63, Dest = 10.172.8.69, Next Protocol = ESP, Packet ID = 57871, Total IP Length = 680

+ Esp: Next Protocol = TCP, SPI = 0xaa4d7f1a, Seq = 0xe

+ Tcp: Flags=...AP..., SrcPort=19511, DstPort=HTTP(80), PayloadLen=617, Seq=1802204223 - 1802204840, Ack=3545752162, Win=65535

- Http: Request, POST /ActiveXTest/Anonymous/WebService.asmx , Using NTLM Authorization

    Command: POST

  + URI: /ActiveXTest/Anonymous/WebService.asmx

    ProtocolVersion: HTTP/1.1

    Accept: */*

    Accept-Language: en-us,zh-cn;q=0.5

    Referer: https://XXXXXXX/ActiveXTest/ajaxClient2.htm

  + SOAPAction: https://tempuri.org/HelloWorld

  + ContentType: text/xml; charset=utf-8

    Accept-Encoding: gzip, deflate

    UserAgent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; GTB6.5; .NET CLR 2.0.50727; .NET4.0C; .NET4.0E; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; MS-RTC EA 2)

    Host: XXXXXXXX

    ContentLength: 0

    Connection: Keep-Alive

    Cache-Control: no-cache

  + Authorization: NTLM

    HeaderEnd: CRLF

 

 

Solution

 There are several options to resolve the issue.

 

l Try to choose Kerberos authentication instead of NTLM.

 

1) On your web server, open “IIS Manager”, open “Authentication” panel for your website and click “Providers…”. Please make sure “Negotiate” is above “NTLM” as below picture.

 

 

 

2) On client machine, open “Internet Options”. On “Advanced” tab, make sure “Enable Integrated Windows Authentication”(requires restart) is checked under “Security” section.

 

 

 

l Try to disable NTLMPreAuth for IE 

 

If the above option doesn’t work in your scenario, please follow the below article to disable NTLMPreAuth. After it is disabled, IE won’t suppress the request body anymore.

https://support.microsoft.com/default.aspx?scid=kb;EN-US;251404

 

l Try to deploy your Web/WCF service to another domain name or port number.

 

If you only have one IIS server and one IP address, please refer to the below article to learn how to host multiple host headers.

https://technet.microsoft.com/en-us/library/cc753195(WS.10).aspx

 

 

More Information

 

Here’re the reproducible codes.

 

Web Service code

using System;

using System.Collections.Generic;

using System.Web;

using System.Web.Services;

 

/// <summary>

/// Summary description for WebService

/// </summary>

[WebService(Namespace = "https://tempuri.org/")]

[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]

// To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line.

// [System.Web.Script.Services.ScriptService]

public class WebService : System.Web.Services.WebService {

 

    public WebService () {

 

        //Uncomment the following line if using designed components

        //InitializeComponent();

    }

 

    [WebMethod]

    public string HelloWorld(String name) {

        return "Hello World " + name;

    }

   

}

 

HTML code

<!DOCTYPE>

<html>

<head>

    <title>Ajax client</title>

    <script type="text/javascript">

 

        function CreateXMLHttpRequest() {

 

            if (typeof XMLHttpRequest != "undefined") {

                //All modern browsers (IE7+, Firefox, Chrome, Safari, and Opera) uses XMLHttpRequest object

                return new XMLHttpRequest();

            }

            else if (typeof ActiveXObject != "undefined") {

                // Internet Explorer (IE5 and IE6) uses an ActiveX object

                return new ActiveXObject("Microsoft.XMLHTTP");

            }

            else {

                throw new Error("XMLHttpRequest not supported");

            }

        }

 

        function CallAjax() {

 

            var objXMLHttpRequest = CreateXMLHttpRequest();

            objXMLHttpRequest.open("POST", "https://XXXXXXX/ActiveXTest/WebService.asmx", true);

            objXMLHttpRequest.setRequestHeader("SOAPAction", "https://tempuri.org/HelloWorld");

            objXMLHttpRequest.setRequestHeader("Content-Type", "text/xml; charset=utf-8");

            //objXMLHttpRequest.setRequestHeader("Expect", "100-continue");

 

            var packet = '<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="https://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="https://www.w3.org/2001/XMLSchema"><soap:Body><HelloWorld xmlns="https://tempuri.org/"><name>abc</name></HelloWorld></soap:Body></soap:Envelope>';

 

            objXMLHttpRequest.onreadystatechange = function () {

                if (objXMLHttpRequest.readyState == 4 && objXMLHttpRequest.status == 200) {

                    var myDiv = document.getElementById("ContentDiv");

                    myDiv.innerText = objXMLHttpRequest.responseText;

                }

 

            }

 

            objXMLHttpRequest.send(packet);

 

        }

    </script>

</head>

<body>

    <input type="button" value="Call Ajax" onclick="CallAjax();" />

    <br />

    <div id="ContentDiv">

    </div>

</body>

</html>

 

 

Regards,

 

ZhiXing Lv from APGC DSI Team