> zejda.net - Web2py and cross-domain scripting

My web2py application is running on more domains. Users wish to move from one domain to another, being still signed-in. I used combination of JS and Ajax to transfer the credentials from sessions between domains, adhering to security measures of JS which do not allow to do it directly.

Cross domain JS is not allowed by most JS implementations because of security. The main idea of my solution is to transfer the necessary data by a script executed in the context of foo iframe to window.location.hash of main bar window.

The cross-domain JS solution

If transfer of credentials is requested (by the program or by user):

1. bar asks for 'credential ticket' in internal frame, calling function "logged" of foo

SPAN(SCRIPT('window.onload = das_ticket_pickup();',_type="text/javascript"),
	IFRAME(_width='0', _height='0', _frameborder='0', _id="serverFrame", 
	_src='http://foo.com/das/logged')) 

2. foo stores & returns the ticket in window.location.hash

# das controller
def logged():
    if not session.user_nick: 
        return dict(resp="nope")
    try:
	# stores 'credential transfer ticket' to db and returns the result
        from random import choice
        import string
        chars = string.letters + string.digits
        ticket = ''.join([choice(chars) for i in range(16)])
        db.das_ticket.insert(person_id=session.user_id, person_nick=session.user_nick,ticket=ticket)
        return dict(resp="%s%s" % (ticket, str(session.user_id)))
    except:
        return dict(resp="err")
# das/logged view
<p name='resp' id='resp'>{{=resp}}</p>
<script type="text/javascript">
window.onload = function() { resp = document.getElementById('resp').innerHTML; if (resp != 'nope') parent.window.location.hash = resp;}
</script>

3. bar waits 2 seconds for the returned ticket

# js
function das_ticket_pickup() {
setTimeout(function() {
	das_ticket_process();
}, 2000); }

4. the ticket pickup

Bar validates and processes the ticket transferred in window.location.hash; if OK, stores the credentials to session, otherwise redirects to login page.

# js
function das_ticket_process()
{
$.ajax({type: "POST", url: '/das/das_ticket_process', data: 'resp='+window.location.hash, success: function(msg) {
if (msg == 'ok') {
	//succesful - temporary arguments in URI may be trimmed
	loc = window.location.href.split('?');
	window.location = loc[0];
} else
	//not successful - redirect to login page
	window.location = 'http://'+document.domain+'/user/login';
} }); }
# das controller
def das_ticket_process():
    try:
        if request.vars.resp == 'err':
            raise 'Error in ticket'
        ticket = request.vars.resp[1:17]
        uid = request.vars.resp[17:]
        # check the ticket
        from datetime import timedelta
        at = db((db.das_ticket.person_id == uid) & (db.das_ticket.ticket == ticket)).select(db.das_ticket.at, db.das_ticket.id)
        if at[0].at < now - timedelta(minutes = 1):
            session.flash = 'Sorry, your login credentials from foo site expired already.'
            return 'err'
        db(db.das_ticket.id == at[0].id).delete()
        pers = load_person_by_id(uid) # function which loads person details from db
        das_acquire_login(pers) # stores credentials to session
        session.user_nick = pers.nick
        session.user_id = pers.id
        session.flash = 'You are logged as %s now.' % pers.nick
        return 'ok'
    except:
        session.flash = "Can't retrieve login credentials from foo site. Please fill the login form instead."
        return 'err'
# das controller
def das_acquire_login(pers):
    username = pers.i.login_email
    request = auth.environment.request
    session = auth.environment.session
    table_user = auth.settings.table_user
    userfield = 'email' 
    passfield = auth.settings.password_field
    user = auth.db(table_user[userfield] == username).select().first()
    if user:
        if not user.registration_key:
            user = Storage(table_user._filter_fields(user, id=True))
            session.auth = Storage(user=user, last_visit=request.now,
                                   expiration=auth.settings.expiration)
            auth.user = user
            return user
    return False