WTForms FormField causing csrf_token errors

Viewed 842

I have a flask project that basically has 2 Form Classes that act as a subform in the third Form Class. I use the 2 subforms with the FormField field. The problem is that when I submit the form, I get csrf_token errors for the two subforms. If I use the csrf=false property in the subforms I don't get this, but does this now mean I am open to CSRF attacks?

class AddressForm(FlaskForm):

    address = StringField(label='Address', validators=[DataRequired()])
    address2 = StringField(label='Address 2', validators=[Optional()])
    city = StringField(label='City', validators=[DataRequired()])
    county = SelectField(label='County', validators=[DataRequired()], choices=[], coerce=int)

    class Meta:
        csrf = False


class NameForm(FlaskForm):
    first_name = StringField(label='First Name', validators=[DataRequired(), Length(min=2)] )
    last_name = StringField(label='Last Name', validators=[DataRequired(), Length(min=2)] )

    class Meta:
        csrf = False

class OrderForm(FlaskForm):

    # Customer Details
    customer_name = FormField(NameForm, separator='_')
    customer_email = EmailField(label='Email', validators=[DataRequired(), Email()])
    customer_mobile_phone = StringField(label='Mobile Phone Number', validators=[DataRequired()])
    customer_alternative_phone = StringField(label='Alternative Phone Number', validators=[Optional()])
    # Delivery Details
    delivery_address = FormField(AddressForm, separator='_')
    area = SelectField(label='Area', validators=[DataRequired()], coerce=int)

I have included the {{ form.hidden_tag() }} in the form and also have a SECRET_KEY configured.

2 Answers

So after a lot of searching and watching videos, I noticed that everything referred to Form and not FlaskForm when doing FormFields. It seems like you can only have one FlaskForm on a page....the subforms must be Form class. I changed my subforms to inherit from Form instead of FlaskForm and it worked. FlaskForm inherits from Form and has csrf enabled and configured by default.

class AddressForm(Form):

    address = StringField(label='Address', validators=[DataRequired()])
    address2 = StringField(label='Address 2', validators=[Optional()])
    city = StringField(label='City', validators=[DataRequired()])
    county = SelectField(label='County', validators=[DataRequired()], choices=[], coerce=int)


class NameForm(Form):
    first_name = StringField(label='First Name', validators=[DataRequired(), Length(min=2)] )
    last_name = StringField(label='Last Name', validators=[DataRequired(), Length(min=2)] )

You include {{ form.csrf_token }} inside your form tag to use CSRF protection. Per the documentation,

When using a FlaskForm, render the form's CSRF field like normal.

<form method="post">
    {{ form.csrf_token }}
</form>

If the template doesn't use a FlaskForm, render a hidden input with the token in the form.

<form method="post">
    <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
</form>

You may find it helpful to refer to the general Flask-WTF documentation on basic usage instructions.

Related