DEV Community

Cover image for πŸ›‘οΈ The Hidden Trap of Django's @login_required Decorator β€” And How It Broke My Auto-Logout πŸ•΅οΈβ€β™‚οΈ
Bharat Solanke
Bharat Solanke

Posted on

πŸ›‘οΈ The Hidden Trap of Django's @login_required Decorator β€” And How It Broke My Auto-Logout πŸ•΅οΈβ€β™‚οΈ

Image description

A few days ago, I was implementing an auto-logout feature in my Django project using JavaScript. The goal was simple:

πŸ“Œ If the user is inactive for some time, log them out automatically without them clicking anything.

Everything seemed to work... until I logged in again and suddenly got logged out again right after logging in 😡.

Here’s what really happened β€” and how @login_required was silently involved.

πŸ” What I Was Trying To Do

I used Django’s built-in logout view inside my own logout view:

# python
from django.contrib.auth import logout
from django.contrib.auth.decorators import login_required

@login_required
def logout_user(request):
    logout(request)
    return redirect("Login:login") 
Enter fullscreen mode Exit fullscreen mode

On the front end, I wrote some JavaScript that checks for inactivity and then hits the logout endpoint:

javascript

fetch("/logout/")
  .then(() => window.location.href = "/login/");

Enter fullscreen mode Exit fullscreen mode

After setting this up, I noticed something strange:

πŸ“Œ After the system auto-logged me out and redirected me to the login page, I entered my credentials β€” but right after that first login, I got logged out again immediately. Just that one time, right after auto-logout. πŸ˜–

πŸ” Debugging the Mystery Logout

I checked browser dev tools and noticed this:

GET /logout/?next=/logout/
Enter fullscreen mode Exit fullscreen mode

Wait… what?! Why is /logout/ redirecting to itself?

🎯 The Culprit: @login_required Decorator

Here's how @login_required works under the hood:

  • It checks if the request.user is authenticated.
  • If not, it redirects to the login page, but with a query param:
/login/?next=/logout/
Enter fullscreen mode Exit fullscreen mode

This is so that after logging in, Django knows where to send the user back.

But what happened here?

  1. My logout_user view had @login_required on it.
  2. When the auto-logout JS was triggered, the session was already expired.
  3. Django saw that the user was not logged in β†’ so it redirected them to:
/login/?next=/logout/
Enter fullscreen mode Exit fullscreen mode
  1. After logging in, Django redirected back to /logout/ because of next=/logout/.
  2. Now the user is logged in β†’ hits the @login_required logout_user view β†’ and gets logged out immediately again.
  3. And the cycle repeats πŸ”

🧩 The Fix: Remove @login_required from Logout

Since logging out doesn't need login, it's perfectly fine to remove @login_required from the logout view.

from django.contrib.auth import logout

def logout_user(request):
    logout(request)
    return redirect("Login:login")

Enter fullscreen mode Exit fullscreen mode

βœ… Now, even if the session expires and JS tries to log the user out, the view still works without triggering a redirect loop.

✨ Bonus: Understanding @login_required and @wraps

If you’re new to @login_required, it’s just a Python decorator.

def login_required(view_func):
    def wrapper(request, *args, **kwargs):
        if request.user.is_authenticated:
            return view_func(request, *args, **kwargs)
        else:
            return redirect("/login/?next=" + request.path)
    return wrapper

Enter fullscreen mode Exit fullscreen mode

Django's version is more robust, of course. But the idea is the same.
Also, decorators often use @functools.wraps to preserve the original function's name and metadata.

from functools import wraps

def my_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        # do something
        return func(*args, **kwargs)
    return wrapper

Enter fullscreen mode Exit fullscreen mode

βœ… Key Takeaways

  • @login_required redirects unauthenticated users to /login/?next=....
  • Be careful using it on views like logout β€” it can cause redirect loops.
  • Auto-logout features should not assume the session is always valid.
  • Always test edge cases around login/logout flows and session expiry.

πŸ’¬ Final Thoughts

Sometimes a single decorator can silently cause big problems β€” especially when it involves session state, login redirects, or auto behaviors. If you ever face "unexpected logout" issues, check your decorators!
Feel free to share if this helped you or if you've run into similar Django surprises 🧠πŸ’₯

Image description

Top comments (0)