Welcome to Django Auth Functional’s documentation!¶
What is this?¶
This library provides a set of decorators for working with authentication and authorization. These decorators can be used to decorate plain functions or method in class-based views and you can decide what http response you want to return in the cases where the authentication/authorization failed.
Authenticating your views¶
In order to authenticate your views all you need to do is decorate your view function:
from auth_functional import authentication
from django.template.response import TemplateResponse
@authentication
def profile(request):
return TemplateResponse(request, 'user/profile.html')
Or, in case you’re using aa class-base view:
from auth_functional import authentication
from django.template.response import TemplateResponse
from django.views.generic import View
class SomeView(View):
@authentication
def get(self, request):
return TemplateREsponse(request, 'user/profile.html')
With that in place, all the non-authenticated requests are gonna receive an HTTP 401 Unauthorized response.
Requesting client authentication¶
When you want the user agent to authenticate itself towards the server, you can send a request for authentication using the WWW-Authenticate header
. Here’s an example using basic authentication:
from auth_functional import authentication
from django.template.response import TemplateResponse
@authentication(www_authenticate='Basic realm="private area"')
def profile(request):
return TemplateResponse(request, 'user/profile.html')
Returning a different response¶
If you want to return a response different than the default HTTP 401 Unauthorized you can provide response_factory
callable to the authentication decorator. If the authentication fails your response_factory
callable will be called with the same parameters as the view.
from auth_functional import authentication
from django.template.response import TemplateResponse
from django import http
def unauthorized_response(request):
response = http.HttpResponse(status=401)
if 'application/json' in request.META.get('HTTP_ACCEPT'):
response['Content-Type'] = 'application/json; charset=utf-8'
return response
@authentication(response_factory=unauthorized_response)
def profile(request):
return TemplateResponse(request, 'user/profile.html')
Authorizing your views¶
In order to authorize your views all you need to do is decorate your view function with the properly named authorization
decorator passing a condition callable that is in charge of allowing or not the access to the resource/controller/store:
from auth_functional import authentication, authorization
from django.template.response import TemplateResponse
def is_staff(request):
return request.user.is_staff
@authentication
@authorization(condition=is_staff)
def profile(request):
return TemplateResponse(request, 'user/profile.html')
Or, in case you’re using aa class-base view:
from auth_functional import authentication, authorization
from django.template.response import TemplateResponse
from django.views.generic import View
def is_staff(request):
return request.user.is_staff
class SomeView(View):
@authentication
@authorization(condition=is_staff)
def get(self, request):
return TemplateREsponse(request, 'user/profile.html')
With that in place, all the non-authorized requests are gonna receive an HTTP 403 Forbidden response which means that the client doesn’t have access.
Returning a different response¶
If you want to return a response different than the default HTTP 403 Forbidden you can provide response_factory
callable to the authorization decorator. If the authorization fails your response_factory
callable will be called with the same parameters as the view.
from auth_functional import authentication, authorization
from django.template.response import TemplateResponse
from django import http
def forbidden_response(request):
response = http.HttpResponse(status=403)
if 'application/json' in request.META.get('HTTP_ACCEPT'):
response['Content-Type'] = 'application/json; charset=utf-8'
return response
def is_staff(request):
return request.user.is_staff
@authentication
@authorization(condition=is_staff, response_factory=forbidden_response)
def profile(request):
return TemplateResponse(request, 'user/profile.html')
Combining multiple conditions¶
You can combine different condition callables by using the and_
, or_
and not_
decorators:
from auth_functional import authentication, authorization, and_, not_
from django.template.response import TemplateResponse
from django import http
def forbidden_response(request):
response = http.HttpResponse(status=403)
if 'application/json' in request.META.get('HTTP_ACCEPT'):
response['Content-Type'] = 'application/json; charset=utf-8'
return response
def is_staff(request):
return request.user.is_staff
def is_admin(request):
return request.user.is_admin
@authentication
@authorization(condition=and_(is_staff, _not(is_admin)), response_factory=forbidden_response)
def profile(request):
return TemplateResponse(request, 'user/profile.html')
Improving performance by using request cache¶
When using multiple conditions you may end repeating operations to fetch the objects and check permissions. Let’s say you have the following two conditions, one that checks the user is the owner of a video, and the other that the user can play the video (some sort of premium feature, whatever):
from auth_functional import authentication, authorization, and_
from django.template.response import TemplateResponse
from django import http
from myapp.models import Video
def can_download_video(request, video_id):
video = Video.objects.filter(pk=video_id).get()
return video.user == request.user
def can_play_video(request, video_id):
video = Video.objects.filter(pk=video_id).get()
return video.has_be_played_by(request.user)
@authentication
@authorization(condition=and_(is_owner_of_video, can_play_video)
def play_video(request, video_id):
return TemplateResponse(request, 'user/video.html')
As you can see, you ended up fetching the same video twice from the database
Video.objects.filter(pk=video_id).get()
. You could create another condition that checks
both conditions but that would miss the point of creating smaller conditions and combine
them with and_
avoiding the conditions to be too coupled with the logic of your app.
A workaround to avoid this duplicated logic to fetch a needed objects is to use the
request.fixture
to fetch and cache the object for the current request:
from auth_functional import authentication, authorization, and_
from django.template.response import TemplateResponse
from django import http
from myapp.models import Video
auth_functional.install_request_fixture('video', Video.objects.get)
def can_download_video(request, video_id):
return request.fixture.video(pk=video_id).user == request.user
def can_play_video(request, video_id):
return request.fixture.video(pk=video_id).has_be_played_by(request.user)
@authentication
@authorization(condition=and_(is_owner_of_video, can_play_video)
def play_video(request, video_id):
return TemplateResponse(request, 'user/video.html')
With that in place the first time you access the fixture your registered callable is going to be called and the result will be cached but only during the current request lifetime. This way any subsequent call to the fixture will return only the cached value.