Ajax request with CORS redirect fails in IE11 - ajax

I'm trying to make an ajax request to a resource on the same domain. Under certain circumstances the request gets redirected(303) to an external resource. The external resource supports CORS.
In browsers like Chrome, Firefox or Safari the request succeeds.
In IE11 the request fails with error:
SCRIPT 7002: XMLHttpRequest: Network Error 0x4c7, The operation was canceled by the user
The ajax request is made with jQuery:
$.ajax({
url: "/data",
type: "POST",
dataType: "json",
contentType: "application/json;charset=UTF-8",
data: JSON.stringify({name: 'John Doe'})
}).done(function () {
console.log('succeeded');
}).fail(function () {
console.log('failed');
});
I've build a little example which demonstrates the problem. You could see the code here.
w/o redirect
w/ redirect
Is there a way to solve this problem? What am I missing?

In the initial definition of the CORS-standard, redirects after a successful CORS-preflight request were not allowed.
IE11 implements this (now outdated) standard.
Since August 2016, this has changed, and all major browsers now support it (Here's the actual pull request).
I'm afraid to support <=IE11 you'll have to modify your server-side code as well to not issue a redirect (at least for <=IE11).
Part 1) Server-side (I'm using node.js express here):
function _isIE (request) {
let userAgent = request.headers['user-agent']
return userAgent.indexOf("MSIE ") > 0 || userAgent.indexOf("Trident/") > 0
}
router.post('data', function (request, response) {
if (_isIE(request)) {
// perform action
res.set('Content-Type', 'text/plain')
return res.status(200).send(`${redirectionTarget}`)
} else {
// perform action
response.redirect(redirectionTarget)
}
})
Part 2 Client-side
Note: This is pure Javascript, but you can easily adapt it to your jQuery/ajax implementation.
var isInternetExplorer = (function () {
var ua = window.navigator.userAgent
return ua.indexOf("MSIE ") > 0 || ua.indexOf("Trident/") > 0
})()
function requestResource (link, successFn, forcedRedirect) {
var http
if (window.XMLHttpRequest) {
http = new XMLHttpRequest()
} else if (window.XDomainRequest) {
http = new XDomainRequest()
} else {
http = new ActiveXObject("Microsoft.XMLHTTP")
}
http.onreadystatechange = function () {
var OK = 200
if (http.readyState === XMLHttpRequest.DONE) {
if (http.status === OK && successFn) {
if (isInternetExplorer && !forcedRedirect) {
return requestResource(http.responseText, successFn, true)
} else {
successFn(http.responseText)
}
}
}
}
http.onerror = http.ontimeout = function () {
console.error('An error occured requesting '+link+' (code: '+http.status+'): '+http.responseText)
}
http.open('GET', link)
http.send(null)
}

its already answered - have a look - https://blogs.msdn.microsoft.com/webdev/2013/10/28/sending-a-cors-request-in-ie/

Related

Ember Simple Auth on Firefox: authentication throws Error

I am extending Ember Simple Auth's base authentication class to allow authentication with Google. So far, it works on Safari 8 and Chrome 41 (both on Yosemite) with no errors. However, on Firefox 35, it throws an Error that does not occur on the other browsers. Here is my Google authenticator class:
App.GoogleAuthenticator = SimpleAuth.Authenticators.Base.extend({
// constants for Google API
GAPI_CLIENT_ID: 'the client id',
GAPI_SCOPE: ['email'],
GAPI_TOKEN_VERIFICATION_ENDPOINT: 'https://www.googleapis.com/oauth2/v2/tokeninfo',
// method for scheduleing a single token refresh
// time in milliseconds
scheduleSingleTokenRefresh: function(time) {
var self = this;
return new Ember.RSVP.Promise(function(resolve, reject) {
Ember.run.later(self, function() {
gapi.auth.authorize({
client_id: self.GAPI_CLIENT_ID,
scope: self.GAPI_SCOPE,
immediate: true
}, function(data) {
if (data && !data.error) {
resolve(data);
} else {
reject((data || {}).error);
}
});
}, time);
});
},
// WIP: recursive method that reschedules another token refresh after the previous scheduled one was fulfilled
// usage: scheduleTokenRefreshes(time until token should refresh for the first time, time between subsequent refreshes)
// usage: scheduleTokenRefreshes(time between refreshes)
scheduleTokenRefreshes: function(time1, time2) {
var self = this;
// if there is a time2, schedule a single refresh, wait for it to be fulfilled, then call myself to schedule again
if (!Ember.isEmpty(time2)) {
self.scheduleSingleTokenRefresh(time1)
.then(function() {
self.scheduleTokenRefreshes(time2);
});
// if there isn't a time2, simply schedule a single refresh, then call myself to schedule again
} else {
self.scheduleSingleTokenRefresh(time1)
.then(function() {
self.scheduleTokenRefreshes(time1);
});
}
},
// method that restores the session on reload
restore: function(data) {
var self = this;
return new Ember.RSVP.Promise(function(resolve, reject) {
console.log(data);
if (Ember.isEmpty(data.access_token)) {
reject();
return;
}
// schedule a refresh 15 minutes before it expires or immediately if it expires in < 15
var timeNow = Math.floor(Date.now() / 1000);
var expiresAt = +data.expires_at;
var timeDifference = expiresAt - timeNow;
var schedulingDelay = Math.floor(timeDifference - 15 * 60);
schedulingDelay = schedulingDelay < 0 ? 0 : schedulingDelay;
self.scheduleTokenRefreshes(schedulingDelay * 1000, 45 * 60);
resolve(data);
});
},
// method that authenticates
authenticate: function() {
var self = this;
return new Ember.RSVP.Promise(function(resolve, reject) {
gapi.auth.authorize({
client_id: self.GAPI_CLIENT_ID,
scope: self.GAPI_SCOPE
}, function(data) {
if (data && !data.error) {
// schedule a refresh in 45 minutes
var schedulingDelay = 45 * 60;
self.scheduleTokenRefreshes(schedulingDelay * 1000);
resolve(data);
} else {
reject((data || {}).error);
}
});
});
},
// method that logs the user out and revokes the token
invalidate: function(data) {
var self = this;
return new Ember.RSVP.Promise(function(resolve, reject) {
// send a GET request to revoke the token
Ember.$.ajax({
type: 'GET',
url: 'https://accounts.google.com/o/oauth2/revoke?token=' + self.get('session.access_token'),
contentType: 'application/json',
dataType: 'jsonp'
})
.done(function(successData) {
resolve(successData);
})
.fail(function(error) {
reject(error);
});
});
}
});
When the popup window closes after a successful login on Google's end, this error appears on Firefox's console:
Error: Assertion Failed: Error: Permission denied to access property 'toJSON' ember.js:13749
"__exports__.default<.persist#http://127.0.0.1/~jonchan/test/bower_components/ember-simple-auth/simple-auth.js:1524:1
__exports__.default<.updateStore#http://127.0.0.1/~jonchan/test/bower_components/ember-simple-auth/simple-auth.js:1195:11
__exports__.default<.setup#http://127.0.0.1/~jonchan/test/bower_components/ember-simple-auth/simple-auth.js:1149:9
__exports__.default<.authenticate/</<#http://127.0.0.1/~jonchan/test/bower_components/ember-simple-auth/simple-auth.js:1066:13
tryCatch#http://127.0.0.1/~jonchan/test/bower_components/ember/ember.js:47982:16
invokeCallback#http://127.0.0.1/~jonchan/test/bower_components/ember/ember.js:47994:17
publish#http://127.0.0.1/~jonchan/test/bower_components/ember/ember.js:47965:11
#http://127.0.0.1/~jonchan/test/bower_components/ember/ember.js:29462:9
Queue.prototype.invoke#http://127.0.0.1/~jonchan/test/bower_components/ember/ember.js:848:11
Queue.prototype.flush#http://127.0.0.1/~jonchan/test/bower_components/ember/ember.js:913:13
DeferredActionQueues.prototype.flush#http://127.0.0.1/~jonchan/test/bower_components/ember/ember.js:718:13
Backburner.prototype.end#http://127.0.0.1/~jonchan/test/bower_components/ember/ember.js:143:11
createAutorun/backburner._autorun<#http://127.0.0.1/~jonchan/test/bower_components/ember/ember.js:546:9
" ember.js:29488
Here is the version information:
DEBUG: Ember : 1.9.1
DEBUG: Ember Data : 1.0.0-beta.14.1
DEBUG: Handlebars : 2.0.0
DEBUG: jQuery : 2.1.3
DEBUG: Ember Simple Auth : 0.7.2
The most confounding thing is that this only appears on Firefox. Is it a bug in Ember Simple Auth or Ember? How do I fix it?
I do not know about only Firefox throwing an error (I've had a similar error with Chrome 40), but there is a bug in ember-simple-auth 0.7.2 with Ember 1.9 that prohibits sending an actual error response in the authenticate method in the authenticator.
If you return reject() in the rejection function of authenticate it will not throw an additional error. This will however not propagate the errorstatus or message, so I consider this a bug.
A work-around was proposed on github about this issue by setting Ember.onerror=Ember.K temporarily so additional errors will not be propagated, although it will propagate the original authenticate rejection with the error-status.
The issue in the github repo only mentions problems with testing this, but I've had this problem in normal code.
see: https://github.com/simplabs/ember-simple-auth/issues/407
Turns out the error was on the resolve part of the authenticate method. Here is what fixed it:
App.GoogleAuthenticator = SimpleAuth.Authenticators.Base.extend({
authenticate: function() {
return new Ember.RSVP.Promise(function(resolve, reject) {
gapi.auth.authorize({
client_id: 'the client id',
scope: ['the scopes'],
}, function(data) {
if (data && !data.error) {
resolve({
access_token: data.access_token // !! passing the entire 'data' object caused the error somehow
});
} else {
reject((data || {}).error);
}
});
});
},
// ...
});
I'm still not quite sure why this caused the error. Perhaps the Google API's response (in its entirety) is somehow incompatible with Ember Simple Auth.

$http error handling: distinguishing user offline from other errors

I have a simple contact form, which Angular submits via AJAX to my app on Google App Engine. (A POST handler uses the send_mail function to email the website owner). This is the client code:
$http.post('', jQuery.param(user), {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}).success(function(data, status, headers, config) {
//...
}).error(function(data, status, headers, config) {
alert('Please check your Internet connection and try again.');
});
Obviously the alert() is handling all errors. Assuming no errors in my server-side code, I'm guessing the chances of App Engine returning anything other than an HTTP status code of 200 is low. However I would still like to distinguish between server errors and the user having lost their connection.
I was thinking of using XMLHttpRequest's textStatus as per this SO answer, but it doesn't appear to be available to the $http.error() callback. I also thought of using Offline.js but I don't need most of what it does so that would seem like wasted bytes in this case.
The $http.error() status I get when offline is 0, but I'm not sure how cross-browser reliable that's going to be. What should I be using?
Before giving you the solution I just wanted to highlight the problems with browser provided is-offline flag
Different browser's offline flag has different meanging
a. for some browsers it means internet is not there
b. for some browsers it means intranet is not there
c. some browsers do allow offline mode, so even if there is no internet, you are not actually offline.
not all browsers support offline in consistent way
Another problem I had with using browser based flag is development scenario, where having internet is not necessary, and that should not trigger the offline mode (for me I block all user interaction If my website goes offline). You can solve this problem by having another indicator telling you if you are in dev/prod, etc.
And most imp part, why do we care to find if browser is in offline mode, is because we do care only if our website is reachable or not, we don't actually care if the internet is there or not. So even if browser tell us it is offline, it is not exactly what we want. there is a tiny difference between what we want and what browser provides.
So considering all of above, I have solved the problem using an offline directive which I am using to block user interaction if user is offline
csapp.directive('csOffline', ["$http", '$interval', "$timeout", "$modal", function ($http, $interval, $timeout, $modal) {
var linkFn = function (scope) {
scope.interval = 10; //seconds
var checkConnection = function () {
$http({
method: 'HEAD',
url: document.location.pathname + "?rand=" + Math.floor((1 + Math.random()) * 0x10000)
})
.then(function (response) {
$scope.isOnline = true;
}).catch(function () {
$scope.isOnline = false;
});
};
scope.isOnline = true;
$interval(checkConnection, scope.interval * 1000);
scope.$watch('isOnline', function (newVal) {
console.log("isOnline: ", newVal);
//custom logic here
});
};
return {
scope: {},
restrict: 'E',
link: linkFn,
};
}]);
I was about to use offline.js, it was too much and most of which I didnt need, so this is the solution I came up with which is purely in angular.js.
please note that http interceptor is invoked during these calls, I wanted to avoid that, hence I had used $.ajax for the calls
$.ajax({
type: "HEAD",
url: document.location.pathname + "?rand=" + Math.floor((1 + Math.random()) * 0x10000),
contentType: "application/json",
error: function () {
scope.isOnline = false;
},
success: function (data, textStatus, xhr) {
var status = xhr.status;
scope.isOnline = status >= 200 && status < 300 || status === 304;
}
}
);
you can replace the logic inside isOnline true/false, with whatever custom logic you want.
I'd go with response.status === 0 check. I've tested it using the code below (needs to be put on a webserver that you can switch on/off at will) and I'm getting 0 in current versions of Google Chrome, Mozilla Firefox, and Internet Explorer. You may use it to test all browsers you want to support.
Code for testing connection status:
<!DOCTYPE html>
<html>
<head>
<title>Connection status test</title>
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.5/angular.min.js"></script>
<script type="text/javascript">
var log = [],
pendingCount = 0,
pendingLimit = 5;
angular.module('app', [])
.run(function ($rootScope) {
$rootScope.log = log;
})
.run(function ($http, $interval, $rootScope) {
$interval(function () {
if (pendingCount >= pendingLimit) {
return;
}
var item = {
time: Date.now(),
text: 'Pending...'
};
++pendingCount;
$http.get('.', {})
.then(function () {
item.text = 'Done';
}, function (response) {
item.text = 'Done (status ' + response.status + ')';
})
.finally(function () {
--pendingCount;
});
log.unshift(item);
}, 1000);
});
</script>
<style rel="stylesheet" type="text/css">
ul {
list-style: none;
padding: 0;
}
</style>
</head>
<body ng-app="app">
<ul>
<li ng-repeat="item in log">{{item.time | date:'HH:mm:ss'}}: {{item.text}}</li>
</ul>
</body>
</html>
try this one
$http.post('', jQuery.param(user), {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}).complete(function(data, status, headers, config) {
//...
});

AJAX Request gets cancelled with AngularJS and Spring Security

We're running an external Grails server-application with the Spring Security plugin.
The front-end is running locally on AngularJS.
Whenever I try to login, the request is immediately canceled.. Remarkably AngularJS sends a GET request first with the OPTIONS method; this returns a 200 OK response just fine.
The actual POST request does never reach the server though... what could possibly cancel my request?
The following code:
$scope.login = function() {
$http.defaults.headers.common["X-Requested-With"] = "XMLHttpRequest";
$scope.loggingIn = true;
// Setup Config
var data = {
j_username: $scope.user.email,
j_password: $scope.user.password
}
var config = {method: 'POST', url: serverUri+'/j_spring_security_check/', data: data};
// Dispatch HTTP Request
$http(config)
.success(function(data, status, headers, config) {
if (data.status) {
// successful login
User.isLogged = true;
User.username = data.username;
}
else {
User.isLogged = false;
User.username = '';
}
$scope.loggingIn = false;
console.log("NOICE!");
})
.error(function(data, status, headers, config) {
$scope.loggingIn = false;
User.isLogged = false;
User.username = '';
if (status == 0) {
// Request got cancelled
console.log("Request got cancelled.");
return;
}
});
}
This is what the canceled request looks like: http://i.stack.imgur.com/kiWnb.png
This is what the OPTIONS request looks like: http://i.stack.imgur.com/FAj96.png
Apparently Chrome does not handle 302 Moved temporarily status codes efficiently when queried by AngularJS in my situation. Firefox properly shows there is a response where Chrome just shows the request as canceled with no response information whatsoever.
This question is solved, but there is still a mystery as to WHY AngularJS does not work. See my question here:
AngularJS $http ajax does not follow Location header

Doing ajax call on response of another function but not working

I have registration form and i have created three function in jquery
First one is validate the form.
Second one is for checking the email uniqueness with ajax request.
Third one is for creating user this also with ajax request.
My flow on submit event is that first i am calling validation function and then on the response of that function i calling the function to check the email uniqueness on the response of this a an ajax request is done to create a user.
First one is validate the form.
function validateregForm()
{
if($('#u_name').val()=="" || !IsEmail($('#u_email').val()) || $('#u_pwd').val().length<6 || $('#c_pwd').val()!=$('#u_pwd').val())
{
if($('#u_name').val()=="")
{
$('#reg_error1').show();
}
if(!IsEmail($('#u_email').val()))
{
$('#email_msg').remove();
$('#reg_error2').show();
}
if($('#u_pwd').val().length<6)
{
$('#reg_error3').show();
}
if($('#u_pwd').val()!=$('#c_pwd').val())
{
$('#reg_error4').show();
}
return false;
}
else
{
return true ;
}
Second one is for checking the email uniqueness with ajax request.
function chkmail(email) {
var posting=$.post('http://localhost/tv100.info/index.php/user/chkmail',{u_email:$('#u_email').val()});
posting.done(function(data){
if(data=='success')
{
$('#email_error').css('display','none');
$('#email_msg').css('display','block');
return true;
}
if(data=='failure')
{
$('#email_msg').css('display','none');
$('#email_error').css('display','block');
return false;
}
});
}
Third one is for creating user this also with ajax request.
$('#regform').submit(function(event) {
var res=validateregForm()
event.preventDefault();
if(res)
{
var email=chkmail();
}
if(email)
{
$('#loading2').show();
var posting=$.post('http://localhost/tv100.info/index.php/user/create_user',$("#regform").serialize());
posting.done(function(data)
{
$('#loading2').hide();
if(data=="success")
{
$('#reg_panel').append('<span id="reg_msg">Registration successful Now You are logged IN</span>');
$('#overlay').fadeOut(300);
$('#login').html('Logout');
$('#sign_in').hide();
$('#cmmnt_field').show();
}
if(data=="failure")
{
$('#reg_panel').append('<span id="res_msg">Something Went Wrong try again Latter</span>');
}
});
}
});
Just telling the case
if(res)
{
var email=chkmail(); // for getting the result in var email, ajax will wait until the success
}
if(email) // In your case before completing the ajax request, javascript come to this line and won't return true. So it it always go to else part.
You can do the user creation on success of chkmail success part. It will work fine
Error in your first line of validateregForm() function,
change
if($('#u_name').val=="" || !IsEmail($('#u_email').val())
to
if($('#u_name').val() =="" || !IsEmail($('#u_email').val())
^ `.val()` here.
You need to learn about asynchronously and synchronously concepts. Ajax calls are usually Asynchronously. Simple set the paramter async as false of each ajax request and you will get the result. From documentation
async (default: true)
Type: Boolean
By default, all requests are sent asynchronously (i.e. this is set to true by default).
If you need synchronous requests, set this option to false.
Cross-domain requests and dataType: "jsonp" requests do not support synchronous operation.
Note that synchronous requests may temporarily lock the browser, disabling any actions while the request is active.
As of jQuery 1.8, the use of async: false with jqXHR ($.Deferred) is deprecated; you must use the success/error/complete callback options instead of the corresponding methods of the jqXHR object such as jqXHR.done() or the deprecated jqXHR.success().
You need to use a callback to process the result of email validation
function chkmail(email, callback) {
var posting = $.post('http://localhost/tv100.info/index.php/user/chkmail', {
u_email : email
});
posting.done(function(data) {
if (data == 'success') {
callback(true);
} else if (data == 'failure') {
callback(false);
}
});
}
$('#regform').submit(function(event) {
var res = validateregForm()
event.preventDefault();
if (res) {
chkmail($('#u_email').val(), function(valid) {
if (valid) {
$('#email_error').css('display', 'none');
$('#email_msg').css('display', 'block');
$('#loading2').show();
var posting = $.post('http://localhost/tv100.info/index.php/user/create_user', $("#regform").serialize());
posting.done(function(data) {
$('#loading2').hide();
if (data == "success") {
$('#reg_panel').append('<span id="reg_msg">Registration successful Now You are logged IN</span>');
$('#overlay').fadeOut(300);
$('#login').html('Logout');
$('#sign_in').hide();
$('#cmmnt_field').show();
}
if (data == "failure") {
$('#reg_panel').append('<span id="res_msg">Something Went Wrong try again Latter</span>');
}
});
} else {
$('#email_msg').css('display', 'none');
$('#email_error').css('display', 'block');
}
});
}
});

How can I send an AJAX request to a node.js server via HTTPS?

I have the following node.js server set up listening to port 9001
var https = require('https');
var fs = require('fs');
var qs = require('querystring');
var options = {
key: fs.readFileSync('privatekey.pem'),
cert: fs.readFileSync('certificate.pem')
};
https.createServer(options, function (req, res) {
res.writeHead(200);
console.log('Request Received!');
console.log(req.method);
if (true || req.method == 'POST') {
var body = '';
req.on('data', function (data) {
body += data;
});
req.on('end', function () {
console.log(body);
var POST = qs.parse(body);
console.log(POST);
});
}
res.end("hello, world\n");
}).listen(9001);
and I am trying to get this server to respond to an AJAX call
function form_save()
{
console.log("submitted!");
var data_obj = {
data1: "item1",
data2: "item2"
}
$.ajax({
url: 'https://adam.testserver.com:9001/',
type: "POST",
dataType: "json",
data: data_obj,
success: function() {
console.log("success!");
},
complete: function() {
console.log("complete!");
}
});
}
There are two problems occurring with my arrangement. The first is that if I start the server and then click the button that triggers my form_save() the node server does not respond and I get the following error:
submitted!
OPTIONS https://adam.testserver.com:9001/ Resource failed to load
jQuery.extend.ajaxjquery.js:3633
$.ajaxjquery.validate.js:1087
form_savew_worksheet.php:64
confirm_deletew_worksheet.php:95
jQuery.event.handlejquery.js:2693
jQuery.event.add.handlejquery.js:2468
w_worksheet.php:73
complete!
At this point if I access that url directy (https://adam.testserver.com:9001/) I get the expected "hello, world" output as well as the console message "Request Received!
GET". From this point on if I click the button to trigger my AJAX call I get a new error.
submitted!
XMLHttpRequest cannot load https://adam.testserver.com:9001/. Origin
https://adam.testserver.com is not allowed by Access-Control-Allow-Origin.
w_worksheet.php:73
complete!
I don't understand why I get this message as both my form and node server reside on the same server. Thanks for taking the time to read, I appreciate any help I can get on this. I've been stuck for a while now!
You've run into the Cross-Origin Resource Sharing (CORS) specification.
Note the OPTIONS in your output. The OPTIONS HTTP Verb is used by the browser to query the web server about the URL, not to GET its contents or POST data to it.
Your server doesn't respond with the correct header data on a CORS request, so your browser assumes it has no rights to access the data, and refuses to GET or POST to the URL.
If you truly want to let any website in the world run that AJAX request, you can do something similar to the following:
function handleOptions(request, response) {
response.writeHead(200, {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Method": "POST, GET, OPTIONS",
"Access-Control-Allow-Headers": request.headers["access-control-request-headers"]
});
response.end();
}
function server(request, response) {
if(request.method == "POST") {
handlePost(request, response);
} else if(request.method == "OPTIONS") {
handleOptions(request, response);
} else {
handleOther(response);
}
}
https.createServer(sslObj, server).listen(9001);
You can fill in the details and whether you should handle GET separately, and so on (handleOther should return an appropriate error code for each request method you don't support).

Resources