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
