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.
- application foo sitting on foo.com
- controller foo/bar sitting on bar.com
- utility controller das is responsible for the transfer of credentials
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