Search code examples
pythondjangomiddleware

Django middleware with login and password which hides all the website pages


I have written following middleware which renders form and asks user for login and password. The middleware should be applied to the whole website:

class InviteLoginForWebsiteMiddleware(object):

    def process_request(self, request):
        if request.session.get('has_invite') == True:
            return None

        form = WebsiteLoginForm()
        extra_context = dict()
        extra_context['form'] = form
        template_name = 'websiteLogin.html'

        if request.method == "POST":
            form = WebsiteLoginForm(request.POST)
            if form.is_valid():
                login = form.cleaned_data['login']
                password = form.cleaned_data['password']

                if login == "mylogin" and password == "mypassword":
                    request.session['has_inv'] = True
                    return None

        return ExtraContextTemplateView.as_view(template_name=template_name, extra_context=extra_context)(request)

The problem with this solution is that when I am creating the form inside process_request csrf token is missing on the rendered page. I have looked for the answer and found that developers recommend to generate form and process it inside process_view

After moving all the code into process_view like:

def process_view(self, request, view_func, view_args, view_kwargs):
    if request.session.get('has_inv') == True:
        return None

    form = WebsiteLoginForm()
    extra_context = dict()
    extra_context['form'] = form
    template_name = 'websiteLogin.html'

    if request.method == "POST":
        form = WebsiteLoginForm(request.POST)
        if form.is_valid():
            login = form.cleaned_data['login']
            password = form.cleaned_data['password']

            if login == "mylogin" and password == "mypassword":
                request.session['has_inv'] = True
                return None

    return ExtraContextTemplateView.as_view(template_name=template_name, extra_context=extra_context)(request)

the code started to work, csrf token was generated and I was able to submit form with login and password.

The problem appears when user enters not valid page like www.mysite.com/notworkingurl/. In this case the process_view is not working, 404 error raised and user is redirected to page which shows partly the interface of the web app. And, of course, I expect that middleware should hide all the pages of the app.

Once again:

  1. When I place the code inside process_request I can catch any call to any page on the website, but csrf is not rendered. As result csrf error raised when user submits form.
  2. When I place the code inside process_view everything is working for the page in case it exists. If it doesn't exist user is redirected according to 404 and other exception urls. And I don't want this redirect to happen.

Can someone suggest any good way to solve this issue?

Solution:

Thanks to suggestion of @knbk one of possible solutions is to use csrf_protect decorator. To make this idea work I have modified my view class as follows:

class ExtraContextTemplateViewCsrfProtect(TemplateView):
  extra_context = None

  @method_decorator(csrf_protect)
  def dispatch(self, request, *args, **kwargs):
    return super(ExtraContextTemplateViewCsrfProtect, self).dispatch(request, *args, **kwargs)

  def get_context_data(self, *args, **kwargs):
    context = super(ExtraContextTemplateViewCsrfProtect, self).get_context_data(*args, **kwargs)
    if self.extra_context:
      context.update(self.extra_context)
    return context

  post = TemplateView.get

Solution

  • Your middleware is not just missing a csrf token, it's vulnerable to CSRF attacks.

    If you want to use this pattern, rather than redirecting to a single login view, you should move all the form logic into the actual view. You can then use csrf_protect to protect the view against CSRF attacks, which also enables you to use the token in the template:

    class InviteLoginForWebsiteMiddleware(object):
    
        def process_request(self, request):
            if request.session.get('has_invite') == True:
                return None
    
            return csrf_protect(CustomLoginView.as_view())(request)
    

    However, I would recommend the method suggested by matox.