[TIMOB-20415] Android: HttpClient Basic Auth: Infinite loop with bad credentials
| GitHub Issue | n/a |
|---|---|
| Type | Bug |
| Priority | High |
| Status | Closed |
| Resolution | Fixed |
| Resolution Date | 2016-06-16T20:25:10.000+0000 |
| Affected Version/s | Release 5.1.2, Release 5.2.2 |
| Fix Version/s | Release 6.0.0, Release 5.4.0 |
| Components | Android |
| Labels | 5.1.2, android, httpclient, network |
| Reporter | Jorge Macias Garcia |
| Assignee | Ashraf Abu |
| Created | 2016-02-16T13:27:19.000+0000 |
| Updated | 2016-06-21T22:42:19.000+0000 |
Description
Steps to Reproduce
- Set bad credentials on Basic Auth Run attatched project with Android and you can see in console readyState = 1. In our log server we register infinite requests and the request never ends. request => response 401 => request => response 401 With no error callback. In iOS its OK.Actual Result
Infinite Loop with no callback to 401 http error.Expected Result
Error callback with 3 attempts like browsers or like iOS.Attachments
| File | Date | Size |
|---|---|---|
| app.zip | 2016-02-16T13:29:52.000+0000 | 10427729 |
Test App
What happen with this? When do you planning to fix this?
[~jormagar] Thanks for reporting this ticket. Will be investigating this.
Please add a unit test for http auth, it is kind of an important core feature. For those who need a fix now, don't use the built-in
withCredentials,username,passwordfeature increateHTTPClient. Instead set the authorization header by hand like so. Do this afterxhr.open()but beforexhr.send().xhr.setRequestHeader("Authorization", "Basic " + Titanium.Utils.base64encode(username + ':' + password));[~sschueller] Thanks for the info. (y)
This bug is affecting Blackbaud's primary mobile application and we need a fix ASAP! This bug is causing our users to get locked out repeatedly and is affecting our user experience rating. Please let us know when this will be fixed, I do not see a planned fixed in version yet. Thank You.
[~jormagar] would you have the logs for the server when this happens? I'm trying to see if it would help in figuring out what's happening.
Yes, i do. When I report the JIRA ticket someone of your team test it. Here the logs: 2016-02-17 08:13:34 W3SVC62 FENABLO 158.42.250.160 POST /error401/ platform=android 443 - 115.127.6.99 HTTP/1.1 Appcelerator+Titanium/5.1.2+(Google+Nexus+7+-+5.1.0+-+API+22+-+800x1280;+Android+API+Level:+22;+en-US; ) - - mimaen.webs.upv.es 401 5 0 389 401 811 2016-02-17 08:13:34 W3SVC62 FENABLO 158.42.250.160 POST /error401/ platform=android 443 - 115.127.6.99 HTTP/1.1 Appcelerator+Titanium/5.1.2+(Google+Nexus+7+-+5.1.0+-+API+22+-+800x1280;+Android+API+Level:+22;+en-US; ) ASPSESSIONIDCWSQRDST=PHKHNFBCPBFPBLNAHHAGIBEG - mimaen.webs.upv.es 401 5 0 389 495 218 ... 2016-02-17 11:01:38 W3SVC62 FENABLO 158.42.250.160 POST /error401/ platform=android 443 - 115.127.6.99 HTTP/1.1 Appcelerator+Titanium/5.1.2+(Google+Nexus+7+-+5.1.0+-+API+22+-+800x1280;+Android+API+Level:+22;+en-US; ) ASPSESSIONIDCWSQRDST=OINLOFBCBNOMGEIHGJKMMCJA - mimaen.webs.upv.es 401 5 0 389 495 218 2016-02-17 11:01:38 W3SVC62 FENABLO 158.42.250.160 POST /error401/ platform=android 443 - 115.127.6.99 HTTP/1.1 Appcelerator+Titanium/5.1.2+(Google+Nexus+7+-+5.1.0+-+API+22+-+800x1280;+Android+API+Level:+22;+en-US; ) ASPSESSIONIDCWSQRDST=PINLOFBCIJIOAJGCCGFNMIIN - mimaen.webs.upv.es 401 5 0 389 495 218 *82706 requests from 115.127.6.99 (Bangladesh) after 3 hours* 2016-02-18 02:28:50 W3SVC62 FENABLO 158.42.250.160 POST /error401/ platform=android 443 - 122.11.137.25 HTTP/1.1 Appcelerator+Titanium/6.0.0+(Nexus+6;+Android+API+Level:+23;+en-US; ) - - mimaen.webs.upv.es 401 5 0 389 366 795 2016-02-18 02:28:50 W3SVC62 FENABLO 158.42.250.160 POST /error401/ platform=android 443 - 122.11.137.25 HTTP/1.1 Appcelerator+Titanium/6.0.0+(Nexus+6;+Android+API+Level:+23;+en-US; ) ASPSESSIONIDAWSSSBTS=EMABDGJCEMCDODGLLKIDDJCM - mimaen.webs.upv.es 401 5 0 389 460 421 ... 2016-02-18 02:30:46 W3SVC62 FENABLO 158.42.250.160 POST /error401/ platform=android 443 - 122.11.137.25 HTTP/1.1 Appcelerator+Titanium/5.1.2+(Nexus+6;+Android+API+Level:+23;+en-US; ) ASPSESSIONIDAWSSSBTS=LOABDGJCOAJEDGMKOFCMJNHH - mimaen.webs.upv.es 401 5 0 389 460 421 2016-02-18 02:30:46 W3SVC62 FENABLO 158.42.250.160 POST /error401/ platform=android 443 - 122.11.137.25 HTTP/1.1 Appcelerator+Titanium/5.1.2+(Nexus+6;+Android+API+Level:+23;+en-US; ) ASPSESSIONIDAWSSSBTS=MOABDGJCCLELCGBKDBDKPADP - mimaen.webs.upv.es 401 5 0 389 460 405 *42 requests from 122.11.137.25 (Singapore) after 2 minutes*
Thank you for the logs.
This seems to be quite a bad bug in Android on certain version when setting the usage of
withCredentialsproperty. Here's a workaround. Please use this:-Instead of relying on the client to set the headers, we set the Authorization headers. This prevents the Infinite loop with bad credentials issue.function request(event) { var client, httpConfig, url, data, credentials, forceBadCredentials; forceBadCredentials = true; //Set value to true to force bug on Android url = "put ur url here"; credentials = { username : (forceBadCredentials) ? "badUser" : "user", password : "p$wd" }; //Dummy post param data = { dummy : 1 }; httpConfig = { onload : onLoad, onerror : onError, onreadystatechange : onReadyStateChange, timeout : 3000 }; client = Ti.Network.createHTTPClient(httpConfig); client.open("POST", url); client.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); client.setRequestHeader("Authorization", "Basic "+ Titanium.Utils.base64encode(credentials.username +':' +credentials.password)); client.send(data); }client.setRequestHeader("Authorization", "Basic "+ Titanium.Utils.base64encode(credentials.username +':' +credentials.password));[~jormagar] Please let me know if this helps in solving your issue.This workaround was mentioned earlier by [~sschueller]
PR Master: https://github.com/appcelerator/titanium_mobile/pull/8050 PR 5_4_X: https://github.com/appcelerator/titanium_mobile/pull/8051 For the url, you can use either the above or run a local node server https://github.com/http-auth/http-auth/blob/master/examples/basic.js, in which case, the url is the ip of your computer. Please test on a device running Android 4.x. Classic app for test:
/** * onReadyStateChange * @description Debug readyState * @param {Object} event */ function onReadyStateChange(event) { Ti.API.info("onReadyStateChange"); var readyState = this.readyState; switch(readyState) { case 0: // after HTTPClient declared, prior to open() // though Ti won't actually report on this readyState Ti.API.info('case 0, readyState = ' + readyState); break; case 1: // open() has been called, now is the time to set headers Ti.API.info('case 1, readyState = ' + readyState); break; case 2: // headers received, xhr.status should be available now Ti.API.info('case 2, readyState = ' + readyState); break; case 3: // data is being received, onsendstream/ondatastream being called now Ti.API.info('case 3, readyState = ' + readyState); break; case 4: // done, onload or onerror should be called now Ti.API.info('case 4, readyState = ' + readyState); Ti.API.info("HTTP DONE WITH STATUS TEXT: " + this.getStatusText()); break; } } /** * onLoad * @description Http Success Callback * @param {Object} event */ function onLoad(event) { Ti.API.info("onLoad"); var statusCode, statusText, responseText, result; statusCode = this.getStatus(); statusText = this.getStatusText(); responseText = this.getResponseText(); result = "onLoad Callback - Status Code: " + statusCode + " - " + "Status Text: " + statusText + " - " + "Response Text: " + responseText; Ti.API.info(result); } /** * onError * @description Http Error callback * @param {Object} event */ function onError(event) { Ti.API.info("onError"); var statusCode, statusText, result; statusCode = this.getStatus(); statusText = this.getStatusText(); result = "onError Callback - Status Code: " + statusCode + " - " + "Status Text: " + statusText; Ti.API.info(result); } function request(event) { var client, httpConfig, url, data, credentials, forceBadCredentials; forceBadCredentials = true; //Set value to true to force bug on Android url = "http://192.168.0.113:1337"; credentials = { username : (forceBadCredentials) ? "badUser" : "user", password : "p$wd" }; //Dummy post param data = { dummy : 1 }; httpConfig = { onload : onLoad, onerror : onError, onreadystatechange : onReadyStateChange, withCredentials : true, username : credentials.username, password : credentials.password, timeout : 3000 }; client = Ti.Network.createHTTPClient(httpConfig); client.open("POST", url); client.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); //client.setRequestHeader("Authorization", "Basic "+ Titanium.Utils.base64encode(credentials.username +':' +credentials.password)); client.send(data); } var win = Ti.UI.createWindow({ layout: 'vertical', backgroundColor: '#fff' }); win.open(); request();Verified the fix. We get a
401 : Unauthorizedcallback. Closing. Environment: Appc Studio : 4.7.0.201606150733 Ti SDK : 5.4.0.v20160617074028, 6.0.0.v20160621124613 Ti CLI : 5.0.9 Alloy : 1.9.0 MAC El Capitan : 10.11.4 Appc NPM : 4.2.7-2 Appc CLI : 5.4.0-18 Node: 4.4.4 Nexus 6 - Android 6.0.1 Samsung Galaxy S3 - Android 4.0.4