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