Vulnerability
Django

Open Redirect in Django Applications

Django's authentication system includes a ?next= parameter for post-login redirects, and Django provides url_has_allowed_host_and_scheme() to validate redirect URLs. However, custom views that use HttpResponseRedirect or redirect() with user input, and applications that override Django's login flow, often introduce open redirect vulnerabilities.

Scan Your Django App

How Open Redirect Manifests in Django

Django's built-in LoginView validates the next parameter against ALLOWED_HOSTS, but custom login views that read next from the query string and pass it directly to HttpResponseRedirect bypass this protection. Views that use redirect() or HttpResponseRedirect with user input from GET or POST parameters are vulnerable. A common pattern is return redirect(request.GET.get('next', '/')) without validation. Django's url_has_allowed_host_and_scheme() exists specifically for redirect validation, but developers often do not know about it or forget to use it. Even when used, passing an empty allowed_hosts set or including overly broad patterns weakens the protection. Custom middleware that redirects based on request parameters (e.g., language selection, region routing) can also introduce open redirects if the target URL is not validated.

Real-World Impact

A Django application's custom login view used return redirect(request.POST.get('next', '/')) after successful authentication. An attacker created a phishing page with a form that POST-ed to the real login endpoint with next=https://evil.com. After the victim entered valid credentials, they were redirected to the attacker's site which prompted for two-factor authentication codes. A Django CMS had a language switcher that redirected to /?lang=en&next=/current-page. By modifying next to an external URL, attackers created phishing links that appeared to originate from the trusted CMS domain.

Step-by-Step Fix

1

Use url_has_allowed_host_and_scheme for all redirects

Django provides this utility specifically for validating redirect targets. Use it in every view that redirects based on user input.

# views.py
from django.http import HttpResponseRedirect
from django.utils.http import url_has_allowed_host_and_scheme
from django.conf import settings

def login_view(request):
    if request.method == 'POST':
        # ... authenticate user ...
        next_url = request.POST.get('next', '')
        if url_has_allowed_host_and_scheme(
            url=next_url,
            allowed_hosts={request.get_host()},
            require_https=request.is_secure(),
        ):
            return HttpResponseRedirect(next_url)
        return HttpResponseRedirect(settings.LOGIN_REDIRECT_URL)
    return render(request, 'login.html')
2

Create a reusable safe redirect helper

Build a utility function that wraps url_has_allowed_host_and_scheme for consistent usage across views.

# utils/redirect.py
from django.http import HttpResponseRedirect
from django.utils.http import url_has_allowed_host_and_scheme
from django.conf import settings

def safe_redirect(request, url_param='next', default=None):
    """Safely redirect to a user-supplied URL or fall back to default."""
    default = default or settings.LOGIN_REDIRECT_URL
    redirect_to = request.GET.get(url_param, '') or request.POST.get(url_param, '')

    if redirect_to and url_has_allowed_host_and_scheme(
        url=redirect_to,
        allowed_hosts={request.get_host()},
        require_https=request.is_secure(),
    ):
        return HttpResponseRedirect(redirect_to)
    return HttpResponseRedirect(default)

# Usage in views
from utils.redirect import safe_redirect

def my_login(request):
    # ... authenticate ...
    return safe_redirect(request, default='/dashboard/')
3

Use Django built-in LoginView

Django's LoginView already handles next parameter validation securely. Use it instead of custom login views.

# urls.py
from django.contrib.auth.views import LoginView

urlpatterns = [
    path('login/', LoginView.as_view(
        template_name='accounts/login.html',
        redirect_authenticated_user=True,
    ), name='login'),
]

# settings.py
LOGIN_REDIRECT_URL = '/dashboard/'
LOGIN_URL = '/login/'

# Django's LoginView automatically validates the ?next= parameter
# against ALLOWED_HOSTS before redirecting.

Prevention Best Practices

1. Always use Django's url_has_allowed_host_and_scheme() to validate redirect URLs. 2. Use Django's built-in LoginView which validates the next parameter automatically. 3. Set LOGIN_REDIRECT_URL in settings.py as a safe default. 4. Never pass request.GET or request.POST values directly to redirect() or HttpResponseRedirect. 5. Include your domain in the allowed_hosts parameter when calling url_has_allowed_host_and_scheme().

How to Test

1. Visit /login?next=https://evil.com, log in, and verify the app does not redirect externally. 2. Try //evil.com, /\evil.com, and javascript:alert(1) as next parameter values. 3. Search your codebase for redirect() and HttpResponseRedirect with request.GET or request.POST. 4. Verify url_has_allowed_host_and_scheme() is used in all custom redirect logic. 5. Use Vibe App Scanner to automatically detect open redirect vulnerabilities in your Django application.

Frequently Asked Questions

Does Django's built-in login prevent open redirects?

Yes. Django's built-in LoginView validates the next parameter against ALLOWED_HOSTS using url_has_allowed_host_and_scheme(). It blocks redirects to external domains. However, if you build a custom login view and handle the next parameter manually without validation, you bypass this protection.

What does url_has_allowed_host_and_scheme check?

It verifies that the URL's host is in the allowed_hosts set and that the scheme is HTTP or HTTPS (or only HTTPS if require_https=True). It also catches protocol-relative URLs (//evil.com) and rejects them. Always pass your request's host in the allowed_hosts parameter.

Can open redirects in Django lead to account takeover?

Yes. Open redirects in login flows can redirect users to phishing pages after authentication, capturing credentials or MFA codes. They can also be chained with OAuth flows to steal authorization codes. An open redirect on a trusted domain is a significant vulnerability, not just a low-severity issue.

Is Your Django App Vulnerable to Open Redirect?

VAS automatically scans for open redirect vulnerabilities in Django applications and provides step-by-step remediation guidance with code examples.

Scans from $5, results in minutes. Get actionable fixes tailored to your Django stack.