It all starts with the Concept of SSO(Single Sign On):
The concept is simple- If there are a set of different apps which
performs validation -while logging in- from the same server ,then if the validation is done by the server once there is
no need to validate the end user again for any other app that uses the same validating server(for a predefined time period, after the time period elapses the validation has to be done again) .How does one ensure
that validation has been done once by the server? Simple - once
successfully validated, the server returns a token which is saved by
the app in a persistent store. This persistent store has to be common
so that the other apps can access this store and if the token is
present in the store the auto validation occurs.
Can
Keychain be used as the required persistent store?
No. The iOS
keychain allows us to share data between applications that share a
common app -id prefix.The apps in question may or may not have the
common app -id prefix.Thus this is not a very optimal solution.However if
the apps in question are guaranteed to use a same app - id keychain
can be used.
Alternate
Solution:
HTML-
5 Local Storage:
Definition
-
Simply
put, it’s a way for web pages to store named key/value pairs
locally, within the client web browser. Like cookies, this data
persists even after you navigate away from the web site, close your
browser tab, exit your browser, or what have you. Unlike cookies,
this data is never transmitted to the remote web server (unless you
go out of your way to send it manually).
-
Mark Pilgrim (Dive into HTML 5)
Mechanism to save Data in HTML5 Local Storage:
The
working of SafariStore(iOS app):
The SafariStore app is designed to work by intercepting the Ajax calls
made by a url shown loaded in a UIWebView. The interceptor logic
works by employing a javaScript injection that captures the Ajax
requests before they hit the server.
Once the Ajax calls are
caught hold of, the SafariStore monitors the response of a Ajax
call which returns a response with the
header OBSSOTOKEN and JSESSIONID. These
are key value pairs which are separated from the header and they are
loaded in the local Storage of the Safari Browser.
Code for Ajax capturing - ajax_handler.js file :
var s_ajaxListener = new Object();
s_ajaxListener.tempOpen = XMLHttpRequest.prototype.open;
s_ajaxListener.tempSend = XMLHttpRequest.prototype.send;
s_ajaxListener.callback = function () {
window.location = 'mpajaxhandler://' + this.url;
}
XMLHttpRequest.prototype.open = function(a,b) {
if (!a) var a='';
if (!b) var b='';
s_ajaxListener.tempOpen.apply(this, arguments);
s_ajaxListener.method = a;
s_ajaxListener.url = b;
if (a.toLowerCase() == 'get') {
s_ajaxListener.data = b.split('?');
s_ajaxListener.data = s_ajaxListener.data[1];
}
}
XMLHttpRequest.prototype.send = function(a,b) {
if (!a) var a='';
if (!b) var b='';
s_ajaxListener.tempSend.apply(this, arguments);
if(s_ajaxListener.method.toLowerCase() == 'post')s_ajaxListener.data = a;
s_ajaxListener.callback();
}
Script Injection when Page loads:
+ (void)initialize {
JSHandler = [NSString stringWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"ajax_handler" withExtension:@"js"] encoding:NSUTF8StringEncoding error:nil] ;
}
- (void)webViewDidStartLoad:(UIWebView *)webView {
//Injecting the javaScript before the view starts to load.Employing Script Injection
[webUIView stringByEvaluatingJavaScriptFromString:JSHandler];
}
Safari
Local Storage with SafariStore:
The
OBSSOToken so collected is sent to a scripting file(.js) which
handles the saving of the token in safari
local cache this
makes calling Safari from the SafariStore essential. This is
achieved through an HTML file saved in the local recourses of
Xcode which calls the above mentioned scripting file.This HTML file
is loaded in the Safari browser when safari is invoked from the SafariStore.This makes sure that the OBSSOTOKEN is stored in
Safari and not locally on a UIWebView.
Code for the .js file:
var result =function (tokenValue) {
//if browser does not support this feature give a error code
if (typeof (localStorage) == 'undefined') {
return 404;
}
else {
try {
localStorage.setItem("ssoToken", tokenValue); //Set the key-value pair
if (localStorage.getItem("ssoToken")) {
//Get the value from storage area
var output = '';
output = localStorage.getItem("ssoToken");
return output;
}
}
catch (e) {
//if the memory is full show a error code
if (e == QUOTA_EXCEEDED_ERR) {
return 400;
}
}
}
};
Code for HTML file calling the .js file:
<!DOCTYPE html>
<html>
<body>
<script src="localStorageScriptHandler.js"></script>
</body>
</html>
Challenges
in the above logical flow:
While
the above logic works flawlessly when executed in a simulator.The
logic fails while executing on an iOS device.The point of failure is
when we try to load the local HTML file in a Safari browser from the
app. The reason for this failure is due to Apple disallowing any app
from running a local scripting file in Safari due to security
reasons.
The following link from Apple communities discuss
this:
https://discussions.apple.com/thread/2206391?start=0&tstart=0
Solution
and Workaround:
While
Apple stops an app from running a local scripting file in Safari it
however does not stop loading a fully qualified web URL from the app
into the safari browser.This brings us to a solution that if we are
able to host our HTML file and the scripting file in a secured-server
which can be reached from our app,we will be able to achieve the
desired result while working with an iOS device.Practically we
managed to create an app which uses DropBox as a
hosting server for the HTML file needed to set
the local storage in mobile safari browser and then another app using
the same hosting server managed to retrieve the value of the
local storage from Safari.
SideEffects:
The
only side effect to the above mentioned solution would be a bad user
experience because a user sees a Safari-browser being launched suddenly from
the app.
Result:
The Final result is awesome. We connect the iOS device that we have been using to our MAC and launch safari. We launch the app from the spring board and the test- url we loaded in the web view comes up. Now we goto the develop option in our MAC safari and we find the name of our app listed ,on clicking it we have a web - inspector pane where we can actually see the OBSSOTOKEN and JSESSIONID stored in local Storage as a key-value pair.