iOS: Authentication using XMLHttpRequest - Handling 401 response - javascript

I'm writing an iOS application using PhoneGap (aka Cordova), I have a simple html login page that logs the user in using an XMLHttpRequest with basic authentication over SSL. Everything works splendidly when you enter your username and password correctly. However, if you enter the wrong username/password none of my callbacks are ever called.
If you run the same code on Chrome for example, with the wrong username/password, chrome behaves in a similar manner, except it pops up an authentication challenge dialog. Hitting cancel on chrome's dialog returns control to my javascript code. Unfortunately, on iOS, the UIWebView wont even popup an auth dialog, it just hangs. I need a way to tell the user that they entered the wrong username or password so they can retry.
The closest thing to an answer I could find was this http://www.freelock.com/2008/06/technical-note-http-auth-with-ajax but changing the response status from the server doesn't seem like the right thing to do.
Here's basically what my request code looks like, but when a bad username or password is sent it never reaches my onload callback (in fact the onreadystatechange callback only gets called once and thats for readyState 1, aka OPEN).
var req = new XMLHttpRequest();
req.onload = function(ev) {
if (req.status == 401) {
alert("Invalid Username/Password");
document.getElementById('password').focus();
} else if (req.status == 200) {
window.location.href = some_secure_site;
} else {
// edit //
alert("Some other status");
}
}
req.onerror = function (ev) { alert('Error'); };
req.ontimeout = function(ev) { alert('Timeout'); };
req.open('GET', uri, true, userValue, passValue);
req.withCredentials = true;
req.send();

A few things became apparent to me while trying to do this on iOS. One is that iOS has a bug relating to basic auth, so if your password has certain special characters in it you'll never get a response back from your server because your server will never get an authentication challenge. That is, if you're using the username and password field in the "open" method.
My guess is they are doing something stupid like sending it via http://username:password#myorigin.com/etc when they should be using http headers and base64 encoding the creds like so
req.setRequestHeader("Authorization", "Basic " + base64(username) + ':' + base64(password));
The other thing I learned is that Basic Auth isnt very secure and is prone to a million and one problems. One of which that will annoy you is that the client will cache the username and password, which will override any new values you send via "req.open(...)". Good luck getting around that using javascript alone, you'll have to do some magic in ObjC to clear the cache.
If you have control over your server, I would suggest using token authentication. Connect over SSL and then send a POST with JSON data containing the username and password. The server could then send back JSON data with an authentication token (essentially a bunch of random characters long enough that it can't ever be guessed, a UUID works well. this is generated by the server and can only be known to the client and the server). Then store the token and the username in the keychain so the user doesnt need to enter their creds everytime they start your app.
My server will always send back a 200 response but the JSON data will contain the information needed to either retry or to store the auth token. In general... basic auth basically sucks.
try {
var req = new XMLHttpRequest();
req.onload = function(ev) {
var response = JSON.parse(this.responseText);
if (response.success === true) {
// The server will respond with a token that will allow us to login
storeCredentials(userValue, response.token);
// redirect with token
else if (req.status == 401) {
alert("Invalid Username/Password");
document.getElementById('password').focus();
} else {
alert("Some other status");
}
}
req.ontimeout = setTimeout(function(ev) { navigator.notification.alert('Timeout trying to contact the server'); }, 10000);
req.onerror = function(ev) { clearTimeout(this.ontimeout); navigator.notification.alert('Error connecting to the server during authentication.'); };
var uri = myWebOrigin + '/authenticate';
req.open('POST', uri, true);
req.setRequestHeader('Cache-Control', 'no-cache');
req.setRequestHeader('Content-Type', 'application/json');
json_data = {username : Base64.encode(userValue), password : Base64.encode(passValue)};
req.send(JSON.stringify(json_data));
} catch(error) {
navigator.notification.alert('Uh oh, an error occurred trying to login! ' + error);
return;
}

I just had the same issues with none of the callbacks being called when using iOS + PhoneGap + jQuery. If I pass incorrect credentials and use
$.ajax({
...
timeout: 5000, // Some timeout value that makes sense
...
});
then the error callback is called with {"readyState":0,"status":0,"statusText":"timeout"}. In that case you would have to guess that the real error is the HTTP 401.
Alternatively you can use
$.ajax({
...
async: false, // :-(
...
});
and your error callback will get something like {"readyState":4,"responseText":"<html>...</html>","status":401,"statusText":"Unauthorized"} back.

To solved this problem remove the header WWW-Authenticate from server response.

There is probably an other HTTP status code beside the 401 and 200 codes received! Make sure there is really no other status code received:
if (req.status == 401) {
alert("Invalid Username/Password");
document.getElementById('password').focus();
} else if (req.status == 200) {
window.location.href = some_secure_site;
} else {
alert('Unfetched status code '+req.status+' captured!');
}

I've been having the same issue - none of the callbacks on the request are being called if invalid credentials are passed in.
My guess is that internally the UIWebView is being told to prompt for credentials, but that prompt is being suppressed, leading to this bug. This is based on all the other browsers I've tried (Safari, Chrome, Firefox) not calling the callbacks in this case until after the prompt is dismissed.
Another solution (the one I'm using) would be to not use Javascript to do the authentication, and instead do it on the iOS side - you can use the code on How to display the Authentication Challenge in UIWebView? as a rough template for doing this.

Related

failed to load "username:password http:// link: Cross origin requests are only supported for protocol schemes http, data, chrome, chrome-extension

My task is to create a web application that lets the user add stuff (such as animals or w/e). To do this I need to use an already built API. The problem is that everytime I try to fetch the information I get errors.
The code is written in javascript. And the task is to use GET, POST and DELETE methods for the API.
The problem is that I keep getting the "Failed to load" error when I try to "send" the request.
Can someone please tell/show me how to do this? I have tried to "Set --allow-file-access-from-file" for chrome but it didn't work.
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = function (){
if (xhr.readyState == 4) {
if (xhr.status == 200) {
console.log(xhr.responseText);
}
if (xhr.status == 404) {
console.log("file is not working bitch");
}
}
}
xhr.open('GET','juraland:28d8d7c89a http://juraland-d887642c13.evalcrew.com/dinosaurs?page[number]=0', true);
xhr.send();
That's not the correct format for username/password urls. They should look like
http://username:password#example.com
But note that this is a very old, deprecated technique, as it sends the username and password in the clear; it is not supported in current browsers:
Use of the format "user:password" in the userinfo field is
deprecated. Applications should not render as clear text any data
after the first colon (":") character found within a userinfo
subcomponent unless the data after the colon is the empty string
(indicating no password). Applications may choose to ignore or
reject such data when it is received as part of a reference and
should reject the storage of such data in unencrypted form. The
passing of authentication information in clear text has proven to be
a security risk in almost every case where it has been used.
If the server you're connecting to depends on basic auth you may be able to instead use the method defined in RFC7617, which replaced the original scheme:
To receive authorization, the client
obtains the user-id and password from the user,
constructs the user-pass by concatenating the user-id, a single
colon (":") character, and the password,
encodes the user-pass into an octet sequence (see below for a
discussion of character encoding schemes),
and obtains the basic-credentials by encoding this octet sequence
using Base64 ([RFC4648], Section 4) into a sequence of US-ASCII
characters ([RFC0020]).
...and then pass that encoded string in an Authentication: Basic header rather than as part of the URL.
The URL which you mentioned is wrong i think
"juraland:28d8d7c89a http://juraland-d887642c13.evalcrew.com/dinosaurs?page[number]=0"
"http://juraland-d887642c13.evalcrew.com/dinosaurs?page[number]=0"
This url is working fine.
But while we are sending this through xhr request, the URL is transforming into this
"https://juraland-d887642c13.evalcrew.com/dinosaurs?page[number]=0"
So can you please make the api call in https format?
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = function (){
if (xhr.readyState == 4) {
if (xhr.status == 200) {
console.log(xhr.responseText);
}
if (xhr.status == 404) {
console.log("file is not working bitch");
}
}
}
xhr.open('GET','http://juraland-d887642c13.evalcrew.com/dinosaurs?page[number]=0', true);
xhr.send();

How to remove automatically added connections proxy in XCC?

I want to make an ajax request from IBM Connections XCC:
let api = 'https://my-server2/api.xml'
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = () => {
if (xmlhttp.readyState == XMLHttpRequest.DONE)
if (xmlhttp.status == 200) {
console.log(xmlhttp.responseText)
}else {
console.log(`Error: ${xmlhttp.readyState}`)
}
}
Result in the network tab is a request to https://connections-host/communities/ajaxProxy/https/my-server2/api.xml so the request is proxied over the connections server. Because of this I get an empty API result since I need an authorized user session. My idea was: The user is logged in in his browser on my-server2 application. So when making an ajax request to my-server2, I can get the API information in his user context.
So my question is: How can I bypass those proxy?
Since I don't set it, I assume that connections manipulate the XMLHttpRequest class in a way like this: https://gist.github.com/dewdad/8830348
I want to view it's code to see the manipulation with this code in the console, but it only shows native code
window.XMLHttpRequest.prototype.open.toString()
"function open() {
[native code]
}"
Connections uses an AJAX proxy to control what's sent out to non-Connections sites/apps. You can configure it for your site to allow specific methods, headers and cookies to be sent to the non-Connections site. I'd take a look at this document on Connections 6.0 https://www.ibm.com/support/knowledgecenter/en/SSYGQH_6.0.0/admin/secure/t_admin_config_ajax_proxy_feature.html
I think that should help you get what you want.

How to bypass authorization using XMLHttpRequest header?

I am trying to access a diagram (the type of url is image), which require authorization, and display it in fancybox. So I tried to use XMLHttpRequest to send authorization in header first and access this url again since the authorization has already bypassed in cache, that's why I set async to false. Here is my code:
function showDiagram(row){
var authCode = $("#authCode").text();
var header = "basic" + authCode;
console.log("In show diagram");
var id = row.children().eq(6).text();
openURL = restURL + "/service/runtime/process-instances/" + id + "/diagram";
console.log(openURL);
var xhr
if(window.XMLHttpRequest){
xhr = new XMLHttpRequest();
}
else{
xhr = new ActiveXObject("Microsoft.XMLHTTP");
}
xhr.open("GET", openURL, false);
xhr.setRequestHeader("Authorization", "Basic " + authCode);
xhr.send();
console.log(xhr.status);
if(xhr.status==200){
callFancybox(openURL);
}else{
alert("The diagram cannot be shown!");
}
}
However, I have met two different error situations.
The browser still ask me to enter username and password when I access the url for the first time, at the same time, console print out status = 200 and fancybox has already popped up, if I refuse to enter password, a GET 401(Unauthorized) error will be returned. If I entered the password, the diagram will show in fancybox and it will work well for all other url I am trying access.
Console print out status = 200 and the fancybox will pop and show "The requested content cannot be loaded.
Please try again later." And a GET 401(Unauthorized) error will be returned.
Why this happening? What should I do to bypass auto login and display the url in fancybox? Thank you very much for any ideas!
You're not specifying what is on the server side, and the problem is there. You must modify your server so that it supports your header authentication. With your current implementation the server is ignoring the headers, and so it's challenging you to indentify in a different way.
Besides you'r not sending any kind of credentials or whatever. You must add headers with that information.

Javascript: How to catch error on page navigated to using window.location.href = url

I am using a REST service to generate a CSV file that I want to prompt the user to download. An example of the service is below:
https://localhost:8444/websvc/exportCSV?viewId=93282392
To prompt the user to download the file, I use this code:
window.location.href = exportUrl, where exportUrl would be a URL like the one above.
This works great if there are no errors on the server when executing the service. The file download prompt appears, the page doesn't refresh, and all is well.
However, if there is an error, I'm getting a nasty HTTP Status 500 page, which is no good for user experience. What I'd like to do is catch any error on the resulting page, and throw up a more friendly error without leaving the current page. I tried:
try {
window.location.href = exportUrl;
}
catch (e) {
alert(e);
}
But that doesn't seem to change the behavior at all. Does anyone have any ideas on how to handle this?
Thanks very much.
Catching an error like that will only catch a JavaScript error. That's not what you're experiencing here. Your server is returning a status code of 500. You need to make sure that everything is good BEFORE you send your users there.
To do that you could effectively 'ping' the URL with Ajax to ensure that it won't return a 500 error.
Something like the following:
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if(xhr.readyState == 4 && xhr.status == 200) {
window.location.href = exportUrl;
}
}
xhr.open('head',exportUrl);
xhr.send(null);
This would do a HEAD request to the URL to ensure that there are no nasty server errors waiting.
Of course, if in the process of actually GENERATING the CSV your server throws an error - it would still return a 500.
A more robust way would be to get the data via Ajax, build a data URL via base64encode and then set window.location.href to that data URL.
By doing that, you could also ensure that the Ajax didn't return a 500 and you got the data you were expecting in the response.
Hope this helps!

Track 404 error pages or How to monitor http header request's status made by the browser using Javascript

I am developing a script that helps track 404 error pages (using JavaScript) at the client side. I searched for tutorials/ posts on the same but didn't find any helpful to me.
One post mentioned using curl (PHP) on the server side & redirect the user to a custom 404 page. But that doesn't address my problem (I want it at the client side).
This page mentions how to create an http object, open up a connection & detect its status in this way:
var request = makeHttpObject();
request.open("GET", "your_url", true);
request.send(null);
....
if( request.status == 404 )
print("404 error")
But over here we ourselves are making the http request & hence have access to the object. My aim is to detect any 404 page errors from the request send by the browser (due to user navigation).
Can we monitor the browser's http object for response statuses? How does Firebug's Network Analysis feature access the http headers? Can I replicate that in my script?
I have noticed that browser's Developer tool's Console gives a 404 error for missing pages as well as individual elements like images on that page.
These are the possible solutions I could imagine:
Monitor the response.status of every http request sent by the browser
Use Developer Tool's/Browser's Error Notification to track 404 errors using an "API" for extracting those error notifications of the tool (if it exists)
Thanks a lot
You mean something like this?
function returnStatus(req, status) {
//console.log(req);
if(status == 404) {
console.log("The url returned status code " + status);
}
else {
console.log("success");
}
}
function fetchStatus(address) {
var client = new XMLHttpRequest();
client.onreadystatechange = function() {
// in case of network errors this might not give reliable results
if(this.readyState == 4)
returnStatus(this, this.status);
}
client.open("HEAD", address);
client.send();
}
fetchStatus("http://yoururl.com");

Categories

Resources