Search code examples
pythondjangopython-2.7http-authentication

Python/Django - HTTP authentication


I want to put basic HTTP authentication somewhere in my code if a certain condition is met. The condition is a request parameter. To simplify, let's say I want it in a Django view.

I know how to do it in PHP. This is the chunk of code which does exactly what I want:

if (isset($_REQUEST['key']) && $_REQUEST['key'] === 'value') {
    if (!isset($_SERVER['PHP_AUTH_USER'])) {
        header('WWW-Authenticate: Basic realm="My Realm"');
        header('HTTP/1.0 401 Unauthorized');
        echo 'Text to send if user hits Cancel button';
        exit;
    } else {
        echo "<p>Hello {$_SERVER['PHP_AUTH_USER']}.</p>";
        echo "<p>You entered {$_SERVER['PHP_AUTH_PW']} as your password.</p>";
    }
}

I can put this chunk of code anywhere in my PHP script and when execution hits those lines, authentication will be requested. If authentication fails, script execution will stop and a 401 Unauthorized header will be sent with the specified message. This is what I want in Python/Django. Here is a chunk of code, help me fill it in:

def some_view(request):
    if request.REQUEST.get('key') == 'value':
        # FILL ME IN

What I want:

  • to be able to put this chunk in code anywhere in code and have it require authentication from user (let's assume request variable is available to me, a django.http.response.HttpResponse instance also, as response)
  • to be able to specify the condition under which authentication will be requested (like the presence of a parameter in the request)
  • to halt further execution if the user supplies invalid credentials and to return a 401 Unauthorized response
  • for the solution to be portable (copy/paste the chunk of code elsewhere and authentication will be triggered there once execution gets to it)

What I DON'T want:

  • to have authentication requirement hardcoded on a call of a function (with a decorator)
  • to change any global settings of my web application
  • to change anything else outside the part of code I want my authentication to trigger
  • to need any additional packages installed for this to work
  • to share credentials of this authentication with the standard authentication for CMS access already set up in the application

Solution

  • from django.http import HttpResponse
    
    def some_view(request):
        if request.REQUEST.get('key') == 'value':
            if not request.user.is_authenticated():
                response = HttpResponse('Text to send if user hits Cancel button', status=401)
                response['WWW-Authenticate'] = 'Basic realm="My Realm'
                return response
            ...
    

    This will return a 401 status code, but requires you to provide your own content/template. If it is acceptable for you to return a 403 status code, you can use the built-in PermissionDenied exception that returns a 403 page based on your '403.html' template:

    from django.core.exceptions import PermissionDenied
    
    def some_view(request):
        if request.REQUEST.get('key') == 'value':
            if not request.user.is_authenticated():
                raise PermissionDenied
            ...
    

    EDIT:

    To implement HTTP Basic Authentication, you have to set up your webserver (Apache, nginx, whatever you're using) to handle this. You should check your webserver's documentation on how to do this.

    Django implements a RemoteUserBackend to allow users to login using basic authentication, but this uses the same user model as the normal ModelBackend, so these are not separate. Luckily, the middleware and the backend are quite simple. The webserver will deny any request with invalid credentials, so any time the 'REMOTE_USER' header (or whatever header value you use) is set, the user is properly authenticated. This header will be available in request.META, so to check if a user is authenticated using Basic Authentication, you can use this code:

    from django.http import HttpResponse
    
    def some_view(request):
        if request.REQUEST.get('key') == 'value':
            if request.META.get('REMOTE_USER', None):
                do_something()
            else:
                response = HttpResponse('Text to send if user hits Cancel button', status=401)
                response['WWW-Authenticate'] = 'Basic realm="My Realm'
                return response
            ...