CSRF with WTforms and webapp2

The WTForms documentation gives a great example for implementing CSRF with a flask :

class MyBaseForm(Form):
    class Meta:
        csrf = True
        csrf_class = SessionCSRF
        csrf_secret = app.config['CSRF_SECRET_KEY']

        @property
        def csrf_context(self):
            return session

      

I would like to do the same, but with webapp2 session instead of flask session.

csrf_context

is a shortcut so you don't have to pass a session every time you create a form. Does anyone know how to create a shortcut like this for a webapp2 session?

Without this shortcut, you need to do something like this when creating the form:

form = MyForm(meta={'csrf_context': self.session})

      

which is a rather complex syntax that I would rather avoid.

+3


source to share


1 answer


I came up with a solution that reduces the awkward syntax described in my question. I changed __init__

my subclass wt.Form

as follows:

class MyBaseForm(wt.Form):
    class Meta:
        csrf = True
        csrf_class = SessionCSRF
        csrf_secret = settings.SESSION_KEY
        csrf_time_limit = timedelta(minutes=60)

    def __init__(self, *args, **kwargs):
        if "session" in kwargs:
            super(MyBaseForm, self).__init__(
                *args, meta={'csrf_context': kwargs["session"]}, **kwargs)
        else:
            super(MyBaseForm, self).__init__(*args, **kwargs)

      

Now when I create the form, I can do this:

form = MyForm(session=self.session)

      

instead of the awkward syntax mentioned in the question.

For processing POSTed form data, I figured out another way to simplify processing.

First I create a form with no non-CSRF fields:



class NoFieldForm(MyBaseForm):
    pass

      

Second, I am creating a decorator that is used for CSRF validation:

def check_csrf(func):
    def wrapper(*args, **kwargs):
        handler = args[0]
        session = handler.session
        request = handler.request
        f = forms.NoFieldForm(request.POST, session=session)
        f.validate()
        if f.csrf_token.errors:
            msg = "The CSRF token expired. Please try again. "
            self.session["msg"] = msg
            self.redirect(self.request.path)
        else:
            func(*args, **kwargs)
    return wrapper

      

Third, I decorate all of my POST handlers:

class SomeHandler(webapp2.RequestHandler):
    @check_csrf
    def post(self):
        pass

      

Let me give you some explanation. The decorator (via a call to the mail manipulator) will receive some form data, but for the purposes of CSRF validation, we are going to throw away all form data except CSRF. We can make it generic by using NoFieldForm, which ignores any other form data that is present. I am using wtforms for validation to make sure the CSRF form field and session token match and that the session token has not expired.

If the CSRF gets through, we call the normal processing handler to process the specific form. If the CSRF fails, we don't call the handler at all, and in my example we redirect back to where we came from with an error message.

+2


source







All Articles