Handling Django Authentication Redirects in HTMX Applications
A clean solution for handling Django authentication redirects in HTMX applications, preventing login forms from appearing in the wrong place when sessions expire.
When a user’s session expires, an HTMX request can follow Django’s login redirect and swap the login page into the target element. I ran into this recently and used middleware to redirect the whole browser instead.
What’s Going Wrong?
HTMX updates part of a page, but an expired session can cause Django to redirect a request to the login page. The returned login HTML can then appear inside a div or table cell rather than replacing the page.
What we really want is to redirect the whole browser to the login page, and then bring users right back to where they were after they log in again. Simple idea, slightly tricky execution.
Redirect with middleware
This middleware converts HTMX 302 responses into a full-page redirect using HX-Redirect. It sets a next parameter from the referring page or request path.
from urllib.parse import urlparse
class HtmxAuthRedirectMiddleware:
"""
Middleware to handle HTMX authentication redirects properly.
When an HTMX request results in a 302 redirect (typically for authentication),
this middleware:
1. Changes the response status code to 204 (No Content)
2. Adds an HX-Redirect header with the redirect URL
3. Preserves the original request path in the 'next' query parameter
This ensures that after authentication, the user is returned to the page
they were attempting to access, maintaining a seamless UX with HTMX.
Credits: https://www.caktusgroup.com/blog/2022/11/11/how-handle-django-login-redirects-htmx/
"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
# HTMX request returning 302 likely is login required.
# Take the redirect location and send it as the HX-Redirect header value,
# with 'next' query param set to where the request originated. Also change
# response status code to 204 (no content) so that htmx will obey the
# HX-Redirect header value.
if request.headers.get("HX-Request") == "true" and response.status_code == 302:
# Determine the next path from referer or current request path
ref_header = request.headers.get("Referer", "")
if ref_header:
referer = urlparse(ref_header)
next_path = referer.path
else:
next_path = request.path
# Parse the redirect URL
redirect_url = urlparse(response["location"])
# Set response status code to 204 for HTMX to process the redirect
response.status_code = 204
# Update the "?next" query parameter
query_params = parse_qs(redirect_url.query)
query_params["next"] = [next_path]
new_query = urlencode(query_params, doseq=True)
# Set the new HX-Redirect header
response.headers["HX-Redirect"] = f"{redirect_url.path}?{new_query}"
return response
This middleware is inspired by this post by the caktus group.
How It Works
The middleware changes a 302 response to 204 so HTMX can process the HX-Redirect header. That header redirects the whole browser. The next parameter records the path to return to after login.
This applies to all HTMX 302 responses, not just authentication redirects. Add checks if other redirects in your application need different handling.
Adding This to Your Project
Add the middleware to your Django project and its MIDDLEWARE list in settings.py:
MIDDLEWARE = [
# Your other middleware stuff...
'yourapp.middleware.HtmxAuthRedirectMiddleware',
]
Just make sure it comes after Django’s authentication middleware in the list - order matters here!
Edits:
2025-05-04: Use urlparse and parse_qs to securely handle urls. Thanks Adam Johnson