Authentication in Web Requests

Django uses sessions and middleware to hook the authentication system into request objects. These provide a request.user attribute on every request which represents the current user. If the current user has not logged in, this attribute will be set to an instance of AnonymousUser, otherwise it will be an instance of User. You can tell them apart with is_authenticated(), like so:

if request.user.is_authenticated():
    # Do something for authenticated users.
else:
    # Do something for anonymous users.

How to Log a User In

To log a user in, from a view, use login(). It takes an HttpRequest object and a User object. login() saves the user’s ID in the session, using Django’s session framework. Note that any data set during the anonymous session is retained in the session after a user logs in. This example shows how you might use both authenticate() and login():

from django.contrib.auth import authenticate, login

def my_view(request):
    username = request.POST['username']
    password = request.POST['password']
    user = authenticate(username=username, password=password)
    if user is not None:
        if user.is_active:
            login(request, user)
            # Redirect to a success page.
        else:
            # Return a 'disabled account' error message
    else:
        # Return an 'invalid login' error message.

How to Log a User Out

To log out a user who has been logged in via login(), use logout() within your view. It takes an HttpRequest object and has no return value. Example:

from django.contrib.auth import logout

def logout_view(request):
    logout(request)
    # Redirect to a success page.

Note that logout() doesn’t throw any errors if the user wasn’t logged in. When you call logout(), the session data for the current request is completely cleaned out. All existing data is removed. This is to prevent another person from using the same Web browser to log in and have access to the previous user’s session data.

If you want to put anything into the session that will be available to the user immediately after logging out, do that after calling logout().

Limiting Access to Logged-In Users

The raw way

The simple, raw way to limit access to pages is to check request.user.is_authenticated() and either redirect to a login page:

from django.shortcuts import redirect

def my_view(request):
    if not request.user.is_authenticated():
        return redirect('/login/?next=%s' % request.path)
    # ...

… or display an error message:

from django.shortcuts import render

def my_view(request):
    if not request.user.is_authenticated():
        return render(request, 'books/login_error.html')
    # ...
The login_required decorator

As a shortcut, you can use the convenient login_required() decorator:

from django.contrib.auth.decorators import login_required

@login_required
def my_view(request):
    ...

login_required() does the following:

  • If the user isn’t logged in, redirect to LOGIN_URL, passing the current absolute path in the query string. Example: /accounts/login/?next=/reviews/3/.
  • If the user is logged in, execute the view normally. The view code is free to assume the user is logged in.

By default, the path that the user should be redirected to upon successful authentication is stored in a query string parameter called “next”. If you would prefer to use a different name for this parameter, login_required() takes an optional redirect_field_name parameter:

from django.contrib.auth.decorators import login_required

@login_required(redirect_field_name='my_redirect_field')
def my_view(request):
    ...

Note that if you provide a value to redirect_field_name, you will most likely need to customize your login template as well, since the template context variable which stores the redirect path will use the value of redirect_field_name as its key rather than “next” (the default). login_required() also takes an optional login_url parameter. Example:

from django.contrib.auth.decorators import login_required

@login_required(login_url='/accounts/login/')
def my_view(request):
    ...

Note that if you don’t specify the login_url parameter, you’ll need to ensure that the LOGIN_URL and your login view are properly associated. For example, using the defaults, add the following lines to your URLconf:

from django.contrib.auth import views as auth_views

url(r'^accounts/login/$', auth_views.login),

The LOGIN_URL also accepts view function names and named URL patterns. This allows you to freely remap your login view within your URLconf without having to update the setting.

Note: The login_required decorator does NOT check the is_active flag on a user.

Limiting access to logged-in users that pass a test

To limit access based on certain permissions or some other test, you’d do essentially the same thing as described in the previous section. The simple way is to run your test on request.user in the view directly. For example, this view checks to make sure the user has an email in the desired domain:

def my_view(request):
    if not request.user.email.endswith('@example.com'):
        return HttpResponse("You can't leave a review for this book.")
    # ...

As a shortcut, you can use the convenient user_passes_test decorator:

from django.contrib.auth.decorators import user_passes_test

def email_check(user):
    return user.email.endswith('@example.com')

@user_passes_test(email_check)
def my_view(request):
    ...

user_passes_test() takes a required argument: a callable that takes a User object and returns True if the user is allowed to view the page. Note that user_passes_test() does not automatically check that the User is not anonymous. user_passes_test() takes two optional arguments:

  1. login_url. Lets you specify the URL that users who don’t pass the test will be redirected to. It may be a login page and defaults to LOGIN_URL if you don’t specify one.
  2. redirect_field_name. Same as for login_required(). Setting it to None removes it from the URL, which you may want to do if you are redirecting users that don’t pass the test to a non-login page where there’s no “next page”.

For example:

@user_passes_test(email_check, login_url='/login/')
def my_view(request):
    ...
The permission_required decorator

It’s a relatively common task to check whether a user has a particular permission. For that reason, Django provides a shortcut for that case – the permission_required() decorator:

from django.contrib.auth.decorators import permission_required

@permission_required('reviews.can_vote')
def my_view(request):
    ...

Just like the has_perm() method, permission names take the form “<app label>.<permission codename>
(i.e. reviews.can_vote for a permission on a model in the reviews application). The decorator may also take a list of permissions. Note that permission_required() also takes an optional login_url parameter. Example:

from django.contrib.auth.decorators import permission_required

@permission_required('reviews.can_vote', login_url='/loginpage/')
def my_view(request):
    ...

As in the login_required() decorator, login_url defaults to LOGIN_URL. If the raise_exception parameter is given, the decorator will raise PermissionDenied, prompting the 403 (HTTP Forbidden) view instead of redirecting to the login page.

Session invalidation on password change

If your AUTH_USER_MODEL inherits from AbstractBaseUser, or implements its own get_session_auth_hash()
method, authenticated sessions will include the hash returned by this function. In the AbstractBaseUser case, this is an Hash Message Authentication Code (HMAC) of the password field.

If the SessionAuthenticationMiddleware is enabled, Django verifies that the hash sent along with each request matches the one that’s computed server-side. This allows a user to log out of all of their sessions by changing their password.

The default password change views included with Django, django.contrib.auth.views.password_change() and the user_change_password view in the django.contrib.auth admin, update the session with the new password hash so that a user changing their own password won’t log themselves out. If you have a custom password change view and wish to have similar behavior, use this function:

django.contrib.auth.decorators.update_session_auth_hash (request, user)

This function takes the current request and the updated user object from which the new session hash will be derived and updates the session hash appropriately. Example usage:

from django.contrib.auth import update_session_auth_hash

def password_change(request):
    if request.method == 'POST':
        form = PasswordChangeForm(user=request.user, data=request.POST)
        if form.is_valid():
            form.save()
            update_session_auth_hash(request, form.user)
    else:
        ...

Since get_session_auth_hash() is based on SECRET_KEY, updating your site to use a new secret will invalidate all existing sessions.