Login

view by view basic authentication decorator

Author:
Scanner
Posted:
May 18, 2007
Language:
Python
Version:
.96
Score:
15 (after 15 ratings)

A simple decorator that requires a user to be logged in. If they are not logged in the request is examined for a 'authorization' header.

If the header is present it is tested for basic authentication and the user is logged in with the provided credentials.

If the header is not present a http 401 is sent back to the requestor to provide credentials.

The purpose of this is that in several django projects I have needed several specific views that need to support basic authentication, yet the web site as a whole used django's provided authentication.

The uses for this are for urls that are access programmatically such as by rss feed readers, yet the view requires a user to be logged in. Many rss readers support supplying the authentication credentials via http basic auth (and they do NOT support a redirect to a form where they post a username/password.)

Use is simple:

@logged_in_or_basicauth def your_view: ...

You can provide the name of the realm to ask for authentication within.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
import base64

from django.http import HttpResponse
from django.contrib.auth import authenticate, login

#############################################################################
#
def view_or_basicauth(view, request, test_func, realm = "", *args, **kwargs):
    """
    This is a helper function used by both 'logged_in_or_basicauth' and
    'has_perm_or_basicauth' that does the nitty of determining if they
    are already logged in or if they have provided proper http-authorization
    and returning the view if all goes well, otherwise responding with a 401.
    """
    if test_func(request.user):
        # Already logged in, just return the view.
        #
        return view(request, *args, **kwargs)

    # They are not logged in. See if they provided login credentials
    #
    if 'HTTP_AUTHORIZATION' in request.META:
        auth = request.META['HTTP_AUTHORIZATION'].split()
        if len(auth) == 2:
            # NOTE: We are only support basic authentication for now.
            #
            if auth[0].lower() == "basic":
                uname, passwd = base64.b64decode(auth[1]).split(':')
                user = authenticate(username=uname, password=passwd)
                if user is not None:
                    if user.is_active:
                        login(request, user)
                        request.user = user
                        return view(request, *args, **kwargs)

    # Either they did not provide an authorization header or
    # something in the authorization attempt failed. Send a 401
    # back to them to ask them to authenticate.
    #
    response = HttpResponse()
    response.status_code = 401
    response['WWW-Authenticate'] = 'Basic realm="%s"' % realm
    return response
    
#############################################################################
#
def logged_in_or_basicauth(realm = ""):
    """
    A simple decorator that requires a user to be logged in. If they are not
    logged in the request is examined for a 'authorization' header.

    If the header is present it is tested for basic authentication and
    the user is logged in with the provided credentials.

    If the header is not present a http 401 is sent back to the
    requestor to provide credentials.

    The purpose of this is that in several django projects I have needed
    several specific views that need to support basic authentication, yet the
    web site as a whole used django's provided authentication.

    The uses for this are for urls that are access programmatically such as
    by rss feed readers, yet the view requires a user to be logged in. Many rss
    readers support supplying the authentication credentials via http basic
    auth (and they do NOT support a redirect to a form where they post a
    username/password.)

    Use is simple:

    @logged_in_or_basicauth
    def your_view:
        ...

    You can provide the name of the realm to ask for authentication within.
    """
    def view_decorator(func):
        def wrapper(request, *args, **kwargs):
            return view_or_basicauth(func, request,
                                     lambda u: u.is_authenticated(),
                                     realm, *args, **kwargs)
        return wrapper
    return view_decorator

#############################################################################
#
def has_perm_or_basicauth(perm, realm = ""):
    """
    This is similar to the above decorator 'logged_in_or_basicauth'
    except that it requires the logged in user to have a specific
    permission.

    Use:

    @logged_in_or_basicauth('asforums.view_forumcollection')
    def your_view:
        ...

    """
    def view_decorator(func):
        def wrapper(request, *args, **kwargs):
            return view_or_basicauth(func, request,
                                     lambda u: u.has_perm(perm),
                                     realm, *args, **kwargs)
        return wrapper
    return view_decorator

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 10 months, 1 week ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 2 weeks ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
  5. Help text hyperlinks by sa2812 1 year, 6 months ago

Comments

Nate (on May 30, 2007):

This is exactly what I needed. In my initial testing it works well but the usage note should be:

@logged_in_or_basicauth()
def your_view:

Note the parentheses. That is because logged_in_or_basicauth is not a decorator but a function which returns a decorator. To specify the HTTP AUTH realm, do this:

@logged_in_or_basicauth('Realm Name')
def your_view:

#

tobias (on January 27, 2009):

has_perm_or_basicauth only checks the permission the first time. After that it just blindly allows access to all authenticated users. You probably want to call test_func again right before you call the view.

#

jamesgpearce (on September 2, 2009):

I love it. Thanks.

#

huseyinalb (on September 21, 2010):

how can i use this on apache using virtualenv? it runs on test server but on apache it asks authentication again and again :)

#

fredz (on September 20, 2011):

To huseyinalb and others : dont forget to put the option

WSGIPassAuthorization On

on your apache conf

#

Leehro (on October 3, 2011):

Yeah, on line 34, you need to check the test_func again.

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

Otherwise, has_perm_or_basicauth will succeed if the user is already authenticated but doesn't have the perm.

#

siberx (on September 24, 2014):

Line 28 was crashing out for me since the return from b64decode is a byte[], not a string. I had to add a decode("ascii") like the following:

uname, passwd = base64.b64decode(auth[1]).decode("ascii").split(':')

#

depaolim (on June 12, 2015):

Nice snippet. Just two questions...

1) I can't see any logout statement... what about the sessions cleanup?

2) the snippet is many years old (2007, wow!), in the meantime ... doesn't exist anything similar in django-core?

#

m7v8 (on December 29, 2015):

I just created a git repository for this to collect all the fixes from the comments and have a working version of this available:

https://github.com/m7v8/django-basic-authentication-decorator

#

Please login first before commenting.