The Mediators
Finally let's look at the mediators. The containing page instantiates an
<iframe> that sources the login-request mediator (Listing Five) to send a message to the login object. loginRequestMediator.html has the same origin as loginPanel.html, which defines the login object. As you can see, the main thing the mediator code does is call the login(...) function that we were just looking at the one in loginPanel.html.
Rounding out the discussion, the login mediator (Listing Six) handles communication from the login object to the containing panel. It works the same as the other mediator, but now it's calling methods of the listener object that I talked about at the beginning of this article. The <iframe> that sources the login mediator uses the same origin as the containing page.
Listing Five
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- This class is used by the login application to talk to the
the surrounding page. It should be loaded from the same
domain as that used by the main page. The url arguments are:
action=[login|logout|recoverPassword]
arg=loginToken (for login)
arg=recoveryMessage (for "recoverPassword")
The action is a message that's sent to the top.loginListener
object. For example, given the url:
http://localhost:8080/loginMediator.html?action=login&arg=theToken
the mediator will effectively execute
top.loginListener.login("theToken");
Note that we can't use the HTML5/Javascript postMessage method, here,
because in Safari at least, a child frame can't use postMessage to
talk to its parent, which is exactly what we need to do.
-->
<html>
<head>
<script type="text/javascript" language="javascript">
function run()
{
var arguments = document.location.search.substring(1); // the "substring(1)" removes the ?
var groups;
groups = arguments.match( /action=([^&]*)/); var action = groups ? groups[1] : null ;
groups = arguments.match( /arg=([^&]*)/); var arg = groups ? groups[1] : null ;
send( action, arg);
}
function send( action, arg )
{
if( window.top.loginListener )
{
if( action == "login" )
window.top.loginListener.login( arg );
else if( action == "logout" )
window.top.loginListener.logout();
else if( action == "recoverPassword" )
window.top.loginListener.recoverPassword( arg );
}
else
{
// SetTimeout is inherently dangerous because it's effectivly running
// eval() when the timeout expires. Imagine, for example, that the
// arg string supplied in the url looks like this:
// "foo" ); doSomethingEvil(
// the argument to set time out is then:
// send("someAction", "foo" ); doSomethingEvil();
// which is bad. This exploit isn't possible if we don't accept
// either an action or argument string which holds a close parenthesis
// that isn't preceded by an open parentheses, thus the test, below.
var parenthesis = /[^\(\)]*([\(\)])/;
var result = action.replace( parenthesis, "$1" );
if( result.charAt(0) != ')' ) // then the action is safe, so execute it
{
result = arg.replace( parenthesis, "$1" );
if( result.charAt(0) != ')' ) // then arg is safe
setTimeout( 'send("'+ action + '","' + arg +'")', 500 );
}
}
}
</script>
</head>
<body onload='run()'>
</body>
</html>
Listing Six
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!--
Instantiate this mediator in an iframe to request that the login panel log you in.
The mediator must be loaded using the same protocol (e.g. https) and domain name as
the loginPanel, and the iFrame that holds the loginPanel must be named "loginPanel"
For example, your main web page would instantiate the login panel like this:
<iframe src="https://secure.iexperiment.net/work/loginPanel.html" name="loginPanel"></iframe>
<div id='requestPanel'><div>
Note that you must use name=, not id=, to identify the iframe. You then request a login from javascript
as follows:
var target = document.getElementById('requestPanel');
target.innerHTML = "<iframe src='https://secure.iexperiment.net/work/loginRequestMediator.html?username=harpo&password=swordfish&remainLoggedIn'></iframe>";
The "remainLoggedIn" argument causes you to remain logged in. If absent, you will not remain logged in.
If the username and password arguments are both missing, then you'll be logged out.
-->
<html>
<head>
<script type="text/javascript" language="javascript">
function run()
{
var arguments = document.location.search.substring(1); // the "substring(1)" removes the ?
var groups;
groups = arguments.match( /username=([^&]*)/); var username = groups ? groups[1] : null ;
groups = arguments.match( /password=([^&]*)/); var password = groups ? groups[1] : null ;
groups = arguments.match( /remainLoggedIn/); var remain = groups ? true : false ;
if( username == null && password == null )
top.frames['loginPanel'].logout(); // remain-logged in is false.
else
top.frames['loginPanel'].doLogin( username, password, remain ); // remain-logged in is false.
}
</script>
</head>
<body onload='run()'>
</body>
</html>
Conclusion
So that's it for login. You now have a secure, self-contained module that you can just plug into your application with about five minutes work. You'll need to do some additional housekeeping, of course (you need to install a certificate into your server so that you can handle https communication and set up a secure subdomain, for example), but most of the grunt work is done for you. Enjoy!
Related Articles
Secure Login in AJAX Applications
Getting Started With the Cloud: Logging On With Google OAuth
Getting Started with Google Apps and OAuth
Getting Started with The Cloud: The Ecosystem
Allen Holub provides technical training, OO design and agile-process consulting, and web application/SaaS development services. He is the author of Holub on Patterns: Learning Design Patterns by Looking at Code (also available for Kindle), C+ C++: Programming With Objects in C and C++, and numerous articles for SD Times, JavaWorld, and IBM Developer Works. Contact him via http://www.holub.com/contact.


