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.
source to share
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.
source to share