Reputation: 16563
The WTForms documentation gives a great example for implementing CSRF with 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'd like to do the same thing but with a webapp2 session instead of a Flask session.
The csrf_context
is a shortcut so that you don't have to pass the session every time that you create a form. Does anyone know how to create such a shortcut for a webapp2 session?
Without this shortcut, you need to do something like this every time you create a form:
form = MyForm(meta={'csrf_context': self.session})
which is some pretty awkward syntax that I'd prefer to avoid.
Upvotes: 3
Views: 386
Reputation: 16563
I've come up with a solution that reduces the awkward syntax described in my question. I've modified __init__
of my subclass of wt.Form
like this:
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 a form, I can do this:
form = MyForm(session=self.session)
instead of the awkward syntax shown in the question.
For processing POSTed form data, I've figured out another technique to simplify processing.
First, I create a form with no fields other than the CSRF field:
class NoFieldForm(MyBaseForm):
pass
Second, I create a decorator that is used to check CSRF:
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 my POST handlers:
class SomeHandler(webapp2.RequestHandler):
@check_csrf
def post(self):
pass
Let me give some explanation. The decorator (via the call to the post webhandler) is going to receive some form data, but for purposes of CSRF checking we are going to throw away all form data except for CSRF. We can make it generic by using the NoFieldForm which ignores any other form data that is present. I use wtforms to check to make sure the CSRF form field and session token match and that the session token isn't expired.
If the CSRF passes, then we call the handler for normal processing to handle the specific form. If the CSRF fails, then we don't call the handler at all, and in my example, we redirect back to where we came from with an error message.
Upvotes: 2