Search code examples
pythondjangodjango-authentication

How do Django authorization decorators (such as: login required) work?


I am trying to better understand "behind the scenes" of the Django authorization decorators. Although I think I understand decorators in general, I find it difficult to understand the authorization decorators code. Is there any "line by line" explanation of the code (https://docs.djangoproject.com/en/2.2/_modules/django/contrib/auth/decorators/)?


Solution

  • I don't know of anywhere that has a line-by-line documentation of these decorators, but here's my take on it.

    def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME):
    

    This function serves as the basis for Django's decorator based authentication. It accepts a test function which will have the user passed to it to determine whether or not that user has access to the decorated view.

        def decorator(view_func):
            @wraps(view_func)
            def _wrapped_view(request, *args, **kwargs):
    

    This chunk of code is just standard Python decorator stuff - if you understand decorators there's not really anything to explain.

                if test_func(request.user):
                    return view_func(request, *args, **kwargs)
    

    Here the test function is called with the user. If the user passes the test the original view function is immediately returned and no further action is taken.

                path = request.build_absolute_uri()
                resolved_login_url = resolve_url(login_url or settings.LOGIN_URL)
    

    Retrieve the current request's url and the login url. The login url can be passed to the user_passes_test decorator or the default value in the Django settings can be used.

                # If the login url is the same scheme and net location then just
                # use the path as the "next" url.
                login_scheme, login_netloc = urlparse(resolved_login_url)[:2]
                current_scheme, current_netloc = urlparse(path)[:2]
    

    Retrieve the HTTP scheme (http or https) and the netloc (www.example.com plus the port if applicable) from both the current url and the login url.

                if ((not login_scheme or login_scheme == current_scheme) and
                        (not login_netloc or login_netloc == current_netloc)):
                    path = request.get_full_path()
    

    If the HTTP scheme and the netloc for the two urls match then the path is set to the relative url rather than the absolute url.

                from django.contrib.auth.views import redirect_to_login
                return redirect_to_login(
                    path, resolved_login_url, redirect_field_name)
    

    Redirect the request to the login page. redirect_to_login will send the user to get logged in with a ?next= GET parameter equal to the current path.

            return _wrapped_view
        return decorator
    

    Finishing decorator stuff.

    login_required is just a shortcut for user_passes_test which already supplies the test_func - a simple function which checks the value of user.is_authenticated.

    permission_required Does the same but takes the name of a permission or a list of permission names and checks that the user has those permissions. permission_required also has the added feature that you can pass raise_exception=True to raise a 403 instead of redirecting to the login url.