简体   繁体   中英

Django: maintain sessions across multiple domains

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.


Current Behaviour

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?

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

Re-direction of the auth

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

Using pixel tags

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.

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/

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM