I have created a REST Web service using DataSnap in Delphi XE. I am calling server methods using the XMLHttpRequest JavaScript object. I am passing the username and password for authentication in the fourth and fifth optional parameters of the XMLHttpRequest Open method.
When I load my DataSnap server in Delphi and attach the debugger to IIS (I am using IIS 7.5) I can see that the first time I call one of the server methods results in the DSAuthenticationManager's OnUserAuthenticate event being called twice.
In the first call the user and password parameters of the OnUserAuthenticate event handler are empty strings. On the second call, the username and password that I passed in the XMLHttpRequest Open method appear in those parameters.
Once the user has been authenticated, any subsequent calls to any of the server methods using XMLHttpRequest results in a single call to the OnUserAuthenticate event handler, with the user's username and password appearing in the user and password parameters, respectively.
I have inspected the source code for a number of the DataSnap classes and have not found code that would cause this effect, which is leading me to think that this behavior may originate in the browser or IIS.
Why is OnUserAuthenticate being called twice upon the first server method call, and what is producing this effect?
In response to Mat DeLong's answer, I am showing one of generic functions I am using to call my server methods. This particular method makes a synchronous call. The baseURL parameter normally is a string similar to http://mydomain.com/myservice/myservice.dll/datasnap/rest/tservermethods1, and method might be myservermethod. Additional parameters are used to pass parameters to the server method:
function getJSONSync(baseURL, method) {
var request = new XMLHttpRequest();
var url = baseURL + method;
for (var i= 2; i < arguments.length; i++) {
url += "/" + arguments[i];
}
request.open("GET", encodeURI(url), false);
request.send(null);
if (request.status != 200) {
throw new Error(request.statusText);
}
return jQuery.parseJSON(request.responseText);
}
Edit: Lex Li appears to be correct, that IIS is only using basic authentication if it fails on the first request. Also, Mat is correct that the OnUserAuthenticate should only be called once per session. After examining the link he provided, it was apparent that I was failing to capture and return the session id. Here is an example of code that works, this time asychronously. It captures the sessionid and stores it in a hidden field. It then passes that session id back in each subsequent call in a Pragma header.
function getJSONAsync(callback, baseURL, method) {
var request = new XMLHttpRequest();
var timedout = false;
request.onreadystatechange = function() {
if (request.readyState !== 4) { return }
if (timedout) { return }
if (request.status >= 200 && request.status < 300)
{
callback(jQuery.parseJSON(request.responseText));
//store the sessionid in a hidden field
$("#sessionid").val(request.getResponseHeader('Pragma').split(',')[0].split("=")[1]);
}
}
var url = baseURL + method;
for (var i= 3; i < arguments.length; i++) {
url += "/" + arguments[i];
}
request.open("GET", encodeURI(url), true);
//pass the sessionid in the Pragma header, if available
if (! $("#sessionid").val() == "") {
request.setRequestHeader("Pragma", "dssession="+$("#sessionid").val());
}
//time out if request does not complete in 10 seconds
var timer = setTimeout(function() { timedout = true; request.abort(); }, 10000);
request.send(null);
}
Edit: When I first posted these two code samples I included sample usernames and passwords in the fourth and fifth parameters of the XMLHttpRequest open method. I was passing these values explicitly during testing. Passing the parameters like this is not recommended, so I removed those parameters in this edit. If you omit these passwords, and don't pass them using another methods (such as in the header of your first REST server method invocation) the browser will display a dialog box asking the user for this information. Once the username and password has been supplied, the browser will remember this information for the remainder of the session. If you close and then reopen the browser and invoke another REST server method, you will once again be challenged for the username and password. This assumes, of course, that you are rejecting invalid users through the OnUserAuthenticate event handler.