I wanted to extend Django’s default Login Required decorator to bypass traditional Django authentication and (given that the application was configured to do so) instead use a session based, one time token presented to the user at application start, akin to a notebook. This isn’t hard to figure out, but since it took me more than a quick moment to look things up, I thought I’d share the complete snippet I came up with.
Customizing Login Required
from django.contrib.auth import REDIRECT_FIELD_NAME from django.shortcuts import render, resolve_url from myapp.settings import cfg from myapp import settings from urllib.parse import urlparse import uuid def login_is_required( function=None, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME ): """ Decorator to extend login required to also check if a notebook auth is desired first (but you could customize this to be another check!) """ def wrap(request, *args, **kwargs): # If we are using a notebook, the user is required to provide a token # This is just an example of what I was trying to do, you could customize this if cfg.NOTEBOOK or cfg.NOTEBOOK_ONLY and not request.session.get("notebook_auth"): request.session["notebook_token"] = str(uuid.uuid4()) print("Enter token: %s" % request.session["notebook_token"]) return render(request, "login/notebook.html") # If the user is authenticated, return the view right away if request.user.is_authenticated: return function(request, *args, **kwargs) # Otherwise, prepare login url (from django user_passes_test) # https://github.com/django/django/blob/master/django/contrib/auth/decorators.py#L10 path = request.build_absolute_uri() resolved_login_url = resolve_url(login_url or settings.LOGIN_URL) login_scheme, login_netloc = urlparse(resolved_login_url)[:2] current_scheme, current_netloc = urlparse(path)[:2] if (not login_scheme or login_scheme == current_scheme) and ( not login_netloc or login_netloc == current_netloc ): path = request.get_full_path() from django.contrib.auth.views import redirect_to_login return redirect_to_login(path, resolved_login_url, redirect_field_name) wrap.__doc__ = function.__doc__ wrap.__name__ = function.__name__ return wrap
And then usage looks like the following:
from myapp.decorators import login_is_required @login_is_required def index(request): return render(request, "main/index.html")
This doesn’t include the logic to check and generate the token, but that’s a different thing! I also made it available in a gist here.