Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 33

Securing Views with LoginRequiredMixin and @login_required

Recommended resources

Hello! In our last lesson, you built a complete user authentication system. Users can now register, log in, and log out of your application. This was a massive step forward!

However, there's a critical security gap: any visitor, logged-in or not, can still see all the pages, including the main task list. Our goal now is to lock down parts of our application so that they are only accessible to authenticated users.

This lesson focuses on protecting your views from unauthorized access. We'll cover the two primary tools Django provides for this, which apply to the two types of views we've used:

  • The @login_required decorator for function-based views.
  • The LoginRequiredMixin for class-based views.

By the end, you'll be able to secure any page in your application, ensuring that only logged-in users can access sensitive content.

1. The Problem: Publicly Accessible Views

Let's imagine you have a view that displays a user's profile. As we saw in the last lesson, we can use {% if user.is_authenticated %} in a template to show or hide a link to that page. But what happens if a logged-out user knows the URL (e.g., /profile/) and types it directly into their browser? Right now, they would either see an error or a broken page, but they wouldn't be stopped from accessing the URL itself.

This is a security vulnerability. We need a way to intercept the request at the view level and redirect any unauthenticated user to the login page.


2. Protecting Function-Based Views with @login_required

The simplest way to protect a function-based view is with a decorator. A decorator is a special Python feature that "wraps" a function, adding new functionality before the original function runs. Django's @login_required decorator does exactly this: it checks if the user is logged in. If they are, it runs the view as normal. If not, it redirects them.

Corey Schafer's tutorial provides an excellent hands-on demonstration of creating a profile page and then securing it.

Python Django Tutorial: Full-Featured Web App Part 7 - Login and Logout System

This video section walks you through the entire process: creating a page that should be private, seeing that it's initially accessible to everyone, and then protecting it with the @login_required decorator. It also covers a crucial configuration step.

Watch the segment 'How to Restrict Routes' from 20:58 to 30:23. Pay close attention to these key moments: The Problem (26:00): Notice how he can access the /profile URL even after logging out. The Solution (26:55): See how the @login_required decorator is imported from django.contrib.auth.decorators and placed directly above the profile view function. The Redirect (28:05): After adding the decorator, trying to access /profile while logged out results in a redirect. Initially, it fails because Django doesn't know where your login page is. The Configuration (28:40): The video shows you how to fix this by adding LOGIN_URL to your settings.py file. This tells Django where to send users who need to log in. The next Parameter (29:30): Observe the URL after being redirected. Django automatically adds ?next=/profile/. This is a fantastic feature that redirects the user back to the page they were originally trying to access after they successfully log in.

Summary of Steps

As you saw in the video, protecting a function-based view involves two simple steps:

  1. Apply the decorator to your view in views.py:

    from django.contrib.auth.decorators import login_required
    from django.shortcuts import render
    
    @login_required
    def profile_view(request):
        # ... your view logic here
        return render(request, 'users/profile.html')
    
  2. Define the login URL in your project's settings.py. This tells the decorator where to send users who aren't logged in.

    # your_project/settings.py
    
    # ... other settings
    
    LOGIN_URL = 'login' # The name of your login URL pattern
    
Django Login Required Decorator Example
This image shows a typical implementation of the `@login_required` decorator. The decorator is placed directly above the view function it needs to protect, and the `login_url` parameter can be used to specify the redirect path.

3. Protecting Class-Based Views with LoginRequiredMixin

Decorators are for functions. But for our project, we've been using Class-Based Views (CBVs) like ListView and CreateView. To protect these, we use a similar concept called a mixin.

A mixin is a special kind of class that provides a set of methods and attributes that can be "mixed in" to another class. The LoginRequiredMixin provides the exact same login-checking functionality as the @login_required decorator, but in a way that works with classes.

This article gives a concise and clear explanation.

Restricting Page Access with LoginRequiredMixin

This resource from 'd-libro' focuses specifically on the LoginRequiredMixin. It breaks down the implementation into two simple steps and clarifies the difference between this approach and the decorator we just learned.

Read the entire short article. Focus on: The Two Steps: Note the process of importing the mixin and then adding it as a parent class to your view. Inheritance Order: Pay close attention to the rule: LoginRequiredMixin must be listed before the main view class (e.g., ListView). This is a common source of errors. The 'Note' at the end: This section reinforces the distinction between the mixin (for classes) and the decorator (for functions).

Let's apply this to the TaskListView that you will build for your task manager. Without protection, anyone could see the list of tasks. Here is how you would secure it:

# your_app/views.py

from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import ListView
from .models import Task # Assuming you have a Task model

class TaskListView(LoginRequiredMixin, ListView):
    model = Task
    template_name = 'your_app/task_list.html'
    context_object_name = 'tasks'
    login_url = '/login/' # Optional: overrides the LOGIN_URL from settings.py

Notice the inheritance: class TaskListView(LoginRequiredMixin, ListView):. By adding LoginRequiredMixin to the list of parent classes (and ensuring it's first), you've successfully protected this view. Any non-authenticated user trying to access the task list will be redirected to the URL specified in LOGIN_URL (or the view's login_url attribute, if provided).

The dev.to article "Get Done ✅" shows this in action within a full to-do list project, which is very similar to your goal. You can see LoginRequiredMixin used consistently across TodoListView, TaskCreate, TaskUpdate, and TaskDelete views.

Test your understanding!

You are given two views in your views.py file:

  1. A class-based view for updating a task: class TaskUpdateView(UpdateView): ...
  2. A simple function-based view that generates a PDF report: def download_report(request): ...

Which protection tool would you use for each, and how would you write the code?

Show answer
  1. For TaskUpdateView, you would use LoginRequiredMixin because it's a class-based view.

    from django.contrib.auth.mixins import LoginRequiredMixin
    from django.views.generic import UpdateView
    
    class TaskUpdateView(LoginRequiredMixin, UpdateView):
        # ... view attributes ...
    
  2. For download_report, you would use the @login_required decorator because it's a function-based view.

    from django.contrib.auth.decorators import login_required
    
    @login_required
    def download_report(request):
        # ... view logic ...
    

Conclusion

Great work! You now have the essential tools to secure your application and ensure that private pages remain private. This is a non-negotiable skill for any web developer.

Key Takeaways:

  • Authorization vs. Authentication: In the last lesson, you implemented authentication (who is the user?). Today, you implemented authorization (what is this user allowed to do?).
  • Function-Based Views: Protect them by applying the @login_required decorator above the function definition.
  • Class-Based Views: Protect them by adding LoginRequiredMixin to the list of parent classes, making sure it comes before the generic view class (like ListView, CreateView, etc.).
  • Configuration is Key: Both methods rely on LOGIN_URL being correctly set in your settings.py file to redirect unauthorized users.

Preview of the Next Lesson:
Our views are now secure, but there's still a problem. If two different users log in, they will both see the same list of all tasks. We've ensured only logged-in users can see the page, but we haven't made the data itself user-specific.

In the next lesson, "Associate application data (e.g., tasks) with the currently logged-in user," we will solve this. You will learn how to link data to a specific user and filter your queries so that users can only see and manage their own information.