Engineering a Cross-Subdomain Authentication Popup System in Django

This document summarizes the technical journey of building a dynamic login popup overlay within a Django project that spans multiple subdomains. The project integrates traditional Django authentication, frontend overlays, and session sharing — all while dealing with CSRF constraints, static page compatibility, and legacy framework limitations (Django 1.x with Python 2.7).

JavaScript-Based Login Popup Overlay

The primary requirement was a JavaScript file that could be injected via <script src="..."> on any page — including static HTML files — to display a login popup to unauthenticated users. This popup needed to:

  • Render dynamically using Django templates
  • Check login status via request.user.is_authenticated
  • Display a styled modal resembling Facebook's login box
  • Support CSRF token handling inside a <form>
  • Be closable by the user

This was accomplished using a Django view that returns JavaScript via render_to_string(). Inside the JS, we inserted a full HTML overlay using overlay.innerHTML with a form, styled buttons, and an embedded CSRF token.

Fixing CSRF Verification Failures

During implementation, attempts to post login forms from static or cross-origin pages caused CSRF errors, especially:

403 Forbidden: CSRF verification failed — Referer mismatch

To resolve this, we updated the settings.py file:

CSRF_TRUSTED_ORIGINS = [
    'https://be.propenda.com',
    'https://heylenvastgoed.propenda.be'
]
SESSION_COOKIE_DOMAIN = '.propenda.be'
CSRF_COOKIE_DOMAIN = '.propenda.be'

However, we discovered the real issue: be.propenda.com and heylenvastgoed.propenda.be are on entirely different top-level domains (.com vs .be), meaning cookies and sessions are not shared.

Temporary Fix: Disabling CSRF for the Login View

To move forward in a non-production environment, we temporarily exempted the login view from CSRF protection using:

from django.views.decorators.csrf import csrf_exempt
url(r'^accounts/login/$', csrf_exempt(LoginView.as_view()), name='account_login')

AMP Compatibility Issue

At one point, an AMP-enabled page caused a failure on login submission due to AMP's strict enforcement of action-xhr for method="POST" forms. AMP was later removed from the page entirely to simplify interaction with Django’s CSRF middleware and restore full HTML compatibility.

Using Iframes for Cross-Origin Login

A major design decision was to embed the login form inside an iframe hosted on the main login domain. Upon successful login, the iframe posts a message to its parent window:

// Inside iframe:
window.parent.postMessage("login-success", "*");
// Parent window:
window.addEventListener("message", function(e) {
  if (e.data === "login-success") {
    document.getElementById("popup-overlay").remove();
    location.reload();
  }
});

This solution sidestepped cookie and session issues caused by domain mismatch between .com and .be.

Alternative Login Technologies Considered

We discussed Shibboleth as a potential federated authentication provider but decided it was unnecessary for this use case. Instead, we focused on pure Django setups:

  • Shared database between Django instances
  • Shared SECRET_KEY to maintain session consistency
  • Matching AUTH_USER_MODEL across subdomains

Final Setup: Two Django Projects, One Auth

The final working architecture includes:

  • Two Django projects: one at be.propenda.com, one at heylenvastgoed.propenda.be
  • Shared PostgreSQL database for user data
  • Shared SECRET_KEY and session settings
  • A popup overlay that conditionally appears using server-rendered JavaScript
  • A login iframe with postMessage() support for seamless cross-page authentication

Next Steps

This implementation is temporary and will eventually be rebuilt. Future improvements may include using OpenID Connect or central authentication via a dedicated subdomain like auth.propenda.be. For now, the system successfully allows login detection and popup control across subdomains, even on static HTML pages.

Comments