Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django context processor and csrf_token

I have a login form that I want to be available in all my views, so I created a context processor to add this form to every loaded context.

The problem is that {% csrf_token %} on the form template won't render the hidden input tag with the CSRF token value.

This is the context_processor order in settings.py:

TEMPLATE_CONTEXT_PROCESSORS = (
    'django.contrib.auth.context_processors.auth',
    'django.core.context_processors.debug',
    'django.core.context_processors.i18n',
    'django.core.context_processors.media',
    'django.core.context_processors.static',
    'django.core.context_processors.tz',
    'django.contrib.messages.context_processors.messages',
    'django.core.context_processors.request',
    'django.core.context_processors.csrf',
    'absolute.context_processors.absolute',
    'myproject.app.context_processors.base',
)

And then the processor itself on app/context_processors.py:

from django.contrib.auth.forms import AuthenticationForm

def base(request):
    context = dict()
    if not request.user.is_authenticated():
        context['login_form'] = AuthenticationForm()
    return context

The form template:

{% load i18n %}

<form method="post" action="{% url "django.contrib.auth.views.login" %}">

    {% csrf_token %}
    <input type="hidden" name="next" value="{% if request.GET.next %}{{ request.GET.next }}{% else %}{{ request.get_full_path }}{% endif %}" />

    {{ login_form.as_p }}

    <input type="submit" class="button success expand" value="{% trans 'Login' %}" />

</form>

The HTML output for this form:

<form action="/accounts/login/" method="post">


    <input type="hidden" value="/" name="next">

    <p><label for="id_username">Usuário:</label> <input type="text" name="username" maxlength="254" id="id_username"></p>
    <p><label for="id_password">Senha:</label> <input type="password" name="password" id="id_password"></p>

    <input type="submit" value="Login" class="button success expand">

</form>

And the error I get when submitting it:

CSRF verification failed. Request aborted.

However, and as I'm only using class-based views, if I add a csrf_protect decorator the form will work, but like this I would have to declare the dispatch method in all my views:

from django.views.decorators.csrf import csrf_protect

class HomeView(TemplateView):
    template_name = 'home.html'

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

Problem status

I gave up from putting the AuthenticationForm on all my views by creating a login form page. Anyway, it would still be awesome if someone could help me find a solution for this problem.

like image 614
vmassuchetto Avatar asked Oct 21 '22 17:10

vmassuchetto


1 Answers

I've spent a couple of hours fighting an issue similar to the one you've described here. The {% csrf_token %} wasn't rendering anything and I was seeing this when in debug mode:

defaulttags.py:66: UserWarning: A {% csrf_token %} was used in a template, but the context did not provide the value. This is usually caused by not using RequestContext.

I was using a simple view that inherited from a TemplateView:

class MenuIndexView(TemplateView):
    template_name = 'menu/index.html'

    def get_context_data(self, **kwargs):
        kwargs = super().get_context_data(**kwargs)

        session = self.request.session
        kwargs['zip_code'] = session.get('zip_code')
        kwargs['calendar'] = helpers.get_menu_calendar(date.today() + timedelta(days=1), timedelta(days=14))
        kwargs['forms'] = {'zip_code': forms.ZipCodeForm({'zip_code': session.get('zip_code')})}

        return kwargs

After fussing around a bit under Django's I realized that pretty much no context at all was available where the tag was being generated (CsrfTokeNode on Django's defaulttags.py file):

class CsrfTokenNode(Node):
    def render(self, context):
        csrf_token = context.get('csrf_token', None)
        if csrf_token:
            if csrf_token == 'NOTPROVIDED':
                return format_html("")
            else:
                return format_html("<input type='hidden' name='csrfmiddlewaretoken' value='{}' />", csrf_token)
        else:
            # It's very probable that the token is missing because of
            # misconfiguration, so we raise a warning
            if settings.DEBUG:
                warnings.warn(
                    "A {% csrf_token %} was used in a template, but the context "
                    "did not provide the value.  This is usually caused by not "
                    "using RequestContext."
                )
            return ''

In this point of the code I was only seeing an item within the context, with a zip_code key.

I opened up the main template file and realized that I was making a rookie mistake - this was my main template menu/index.html:

{% extends 'base.html' %}

{% block content %}
    <div class="menu-order-meta zip_calendar">
        <div class="ink-grid align-center">
            <div class="column-group gutters half-vertical-padding medium">
                {% include 'partials/menu/zip-code.html' with zip_code=zip_code only %}
            </div>
        </div>
    </div>
{% endblock %}

I was including the form through a partial template and I was explicitly restricting the available context within that partial template - notice the with zip_code=zip_code only declaration - (and by doing so, implicitly turning the csrf_token context processor unavailable).

like image 141
Diogo Avatar answered Oct 23 '22 09:10

Diogo