The Adobe AIR project I'm working on needs to authenticate with an appliance that my company sells. The form on the appliance's login page executes some fancy Javascript prior to submitting the form, which results in an additional session cookie being generated. The simplest approach seemed to be to use an HTMLLoader object to access the login page, fill out the form variables, and submit the form.
After a successful login I could extract the session cookies and tack them onto subsequent requests I was making with Flex's HTTPService. Doing all of this turned out to be pretty easy. Here's an outline of what I did, followed by some code snippets.
In my AIR application I am not rendering any of the HTML retrieved by the HTMLLoader. I'm just using it for authentication, so all of this happens behind the scenes and is not visible to the AIR app's user.
- Examine the structure of the login form, paying attention to any form variables and
onSubmitmethods required by the form. - Instantiate an
HTMLLoader, setup an event listener to handle theEvent.COMPLETEevent, and load the page. - In the event handler, use DOM methods to specify values for the required form variables.
- If the form has an
onSubmitfunction, call this function before you submit the form. When you use the DOM to submit a form, theonSubmitfunction is not executed like it normally would by a browser. - Submit the HTML form.
- Extract any cookies and store them in a generic object. These cookies will be needed for any
HTTPServicethat you'll be using after authentication. - Use your HTTPService to fetch data as usual.
Note that there is one drawback to this approach. The HTMLLoader doesn't seem to fire an event if your page cannot be loaded. In my AIR app, the user supplies an IP address for the appliance they wish to authenticate with. If the user supplies an invalid IP, the HTMLLoader won't find any page to load and will never fire off an event to indicate that it failed to load the page.
Check out this nice post which describes some options for detecting when the page cannot be loaded.
Here's the code relating to the HTMLLoader, which I've taken from my Caringorm Command class's execute method. Forgive me for not showing the entire class...
import com.adobe.cairngorm.commands.ICommand;
import com.adobe.cairngorm.control.CairngormEvent;
import flash.events.Event;
import flash.html.HTMLLoader;
import flash.net.URLRequest;
// my Cairngorm model locator class which stores my app's variables
import com.sunild.model.AppModelLocator;
private modelLocator:AppModelLocator = AppModelLocator.getInstance();
private var loginAttempted:Boolean = false;
public function execute( event:CairngormEvent ):void {
var htmlLoader:HTMLLoader = new HTMLLoader();
// the appliance IP address is stored in my Cairngorm model locator class
var url:URLRequest = new URLRequest("https://" + modelLocator.applianceIp);
htmlLoader.addEventListener(Event.COMPLETE, handleHtmlLoaderComplete);
htmlLoader.load(url);
}
/*
This method gets called anytime content in the HTMLLoader object changes.
That can happen if: we submit a form, get redirected, or otherwise navigate
to some other page. This function therefore needs logic to detect what
page it's processing.
*/
private function handleHtmlLoaderComplete( event:Event ):void {
var ldr:HTMLLoader = event.target as HTMLLoader;
var doc:Object = ldr.window.document;
// my login form is found in a frame on the login page
var frame:Object = ldr.window.frames;
// a simple method of determining which page we're processing
trace("Title of page being loaded: " + doc.title);
// login page, frame[0] has a form named "loginForm"
if ( !loginAttempted && frame[0] && frame[0].loginForm ) {
// set the userName and password form variables
frame[0].loginForm.userName.value = modelLocator.username;
frame[0].loginForm.password.value = modelLocator.password;
// execute the form's onSubmit function if necessary
frame[0].onSubmitFunction();
// keep track of whether we submitted the form so we only do it once
loginAttempted = true;
frame[0].loginForm.submit();
}
else if (loginAttempted && frame[0] && frame[0].loginForm) {
// if we attempted to login but still see the login form, the login attempt failed
// set loginAttempted to false
loginAttempted = false;
// add code here to handle failed login
// I'm removing the event listener, probably isn't necessary here
ldr.removeEventListener(Event.COMPLETE, handleHtmlLoaderComplete);
}
else {
// look for something on the page to verify that we were logged in
// I'm simply using the page's title
if ( String(doc.title).search("Authentication Succeeded") ) {
// extract the session cookies and store in an anonymous object
var tmpCookies:Array = String(doc.cookie).split("; ");
for each ( var tmpString:String in tmpCookies ) {
var tmpArray:Array = tmpString.split("=");
modelLocator.cookieObject[ tmpArray[0] ] = tmpArray[1];
}
/* When we get to this point, we are logged in ... now we should clean up
and unregister the event handler for the this HTMLLoader object
*/
ldr.removeEventListener(Event.COMPLETE, handleHtmlLoaderComplete);
}
}
}
And later in my AIR application, I can use an HTTPService to request data from the appliance. Here's an MXML snippet that defines the HTTPService which includes the cookie object we created above. Now when I call the HTTPService's send() method, my session cookies will be included for me!
<mx:HTTPService id="fetchSomeData"
url="https://{modelLocator.applianceIp}/some_path_to_data.xml"
resultFormat="e4x"
headers="{modelLocator.cookieObject}" />
This all seems a little hoaky, and is not the final code that we'll be using in the AIR application. Eventually, authentication with the appliance will all be done via an HTTPService that posts XML data to the appliance. But this functionality has not been implemented on our appliance yet!
Manipulating the DOM with an HTMLLoader is quite easy, so this seemed like a good solution to move the project along in the meantime.
No comments:
Post a Comment