Reputation: 88499
I have two/multiple domains say, foo.com
and bar.com
and both have the same backend, which means both domains redirect the coming requests to same "web instance" hosted somewhere else.
If a user login in foo.com
, he/she also need to login in bar.com
in order to access any end-point/URL such as bar.com/some/url/end-point/
.
The SESSION_COOKIE_DOMAIN
may do something if I've the domains with a common pattern. Unfortunately, I don't.
Question
How can I maintain user sessions across multiple domains?
Upvotes: 7
Views: 3523
Reputation: 2589
This is an interesting question. There should be many ways of doing it, the first thing that comes to my mind is to use an iframe
. The example below is tested with Django 2.2
.
In your settings.py
, expose your sessionid
to javascript.
SESSION_COOKIE_HTTPONLY = False
In your view, be sure to put xframe_options_exempt
on, or django will not allow it to be "iframed" from another domain, here I use a template view, so I put the decorator in urls.py
instead.
from django.views.decorators.clickjacking import xframe_options_exempt
urlpatterns = [
path(
'other_domain/',
xframe_options_exempt(TemplateView.as_view(template_name='examplesite/otherdomain.html')),
name='other_domain',
)
# ...
]
domains
is a list of all of the other domains (not including the one your user is on right now), in your template, expose them in the <head>
tag.
<head>
{{ domains|json_script:"domains" }}
{{ other_domain_path|json_script:"other-domain-path"}}
</head>
this will become something like this:
<script id="domains" type="application/json">["c222dbef.ngrok.io"] </script>
<script id="other-domain-path" type="application/json">"/other_domain/"</script>
Then in your javascript:
(function() {
function getCookie(cname) { //copied from w3schools
var name = cname + "=";
var decodedCookie = decodeURIComponent(document.cookie);
var ca = decodedCookie.split(";");
for (var i = 0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == " ") {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
return c.substring(name.length, c.length);
}
}
return "";
}
function postSessionID(id) {
var domains = JSON.parse(document.getElementById("domains").textContent);
var path = JSON.parse(document.getElementById("other-domain-path").textContent);
domains.forEach(function(domain) {
var src = "https://" + domain + path;
var iframeEl = document.createElement("iframe");
iframeEl.setAttribute("class", "invisible");
iframeEl.setAttribute("src", src);
(function(id) { // this is an async call in a loop, create a closure here to protect "id"
iframeEl.addEventListener("load", function() {
this.contentWindow.postMessage(id, this.getAttribute("src"));
});
})(id);
document.body.appendChild(iframeEl);
});
}
function main() {
var sessionID = getCookie("sessionid");
if (!sessionID) {
return;
}
postSessionID(sessionID);
}
main();
})();
The idea of the above code is to create iframes for each other domains, the src of the iframes are pointing to our view
named "other_domain". Once the iframes are loaded, we use postMessage
to send session id to them.
In examplesite/otherdomain.html
:
<head>
{{ domains|json_script:"domains" }}
{# we also need to expose all other domains #}
</head>
in your script:
(function() {
function setCookie(cname, cvalue, exdays) {
var d = new Date();
d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toUTCString();
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
}
var domains = JSON.parse(document.getElementById("domains").textContent);
var trustedSources = domains.map(function(domain) {
return "https://" + domain;
});
window.addEventListener("message", function(e) {
if (!e.origin in trustedSources) {
return; // this prevents setting session id from other source
}
var sessionID = e.data;
// you can probably get your cookie expiry from your django view, so all of your cookie expires at the same time
setCookie("sessionid", sessionID, 365);
}, false);
})();
Now your users can log in and log out from any of your domains, and they'll have the same session across all of your domains.
I'm posting the full example in my github: https://github.com/rabbit-aaron/django-multisite-sign-in
Follow readme.md
to set it up.
Upvotes: 0
Reputation: 146510
When you look at it from a security perspective this is a risk as such, where one domain by any workaround can read cookies from another domain. So for the obvious reason, this doesn't work normally.
Now in most cases, the only thing you would like to share is a token or session id. So you can approach this problem in different ways
Let's assume your token is generated using example.com/auth
. This url can return the token in cookies as well as json response. You can then also make this url return a 301 to example.org/preauth?token=XXX
. This url would then set the cookies with the token
So basically, in this case, you can handle the whole approach at server side itself
In this case, what you want to do is have a pixel tag url. Once you have received the auth token by doing auth on example.com/auth
You will add one image source tag on the page dynamically using javascript to your other domain
<img src='http://example.org/cookiepixel?token=yyy' />
This would then return the cookie which will be set in example.org
instead of example.com
In this approach, you are dependent on client side code to make sure the cross-domain auth happens.
Upvotes: 5
Reputation: 3655
I don't think you can do single sign on across completely different domains. But maybe you could use OAuth authentication, with both domains pointing to the same OAuth provider? Then implement an OAuth provider that generates the same access token for either domain. I have no idea how much effort that might be though.
https://django-oauth-toolkit.readthedocs.io/en/latest/
Upvotes: 0