Pre-populate a WTForms Form And Prevent CSRF Attacks
Get the project source code below, and follow along with the lesson material.
Download Project Source CodeTo set up the project on your local machine, please follow the directions provided in the README.md
file. If you run into any issues with running the project source code, then feel free to reach out to the author in the course's Discord channel.
Editing Data with Forms
You can use WTForms to prepopulate data for forms as well, which is handy when you are editing records. To prepoulate data in a form from a object, WTForms provides a method called process
where we can pass in raw form data and/or an object
@products.route('/<product_id>/edit', methods=['GET', 'POST'])
def edit(product_id):
product = Product.query.get_or_404(product_id)
form = ProductForm()
if form.validate_on_submit():
product.name = form.name.data
product.description = form.description.data
db.session.add(product)
db.session.commit()
elif not form.errors:
# We do this to make sure we're not overwriting a user's input
# if there was an error.
form.process(formdata=form.data, obj=product)
return render_template('products/edit.html', form=form, product=product)
A shortcut for the above logic, is to simply pass in the object when instantiating the form.
@products.route('/<product_id>/edit', methods=['GET', 'POST'])
def edit(product_id):
product = Product.query.get_or_404(product_id)
form = ProductForm(product)
if form.validate_on_submit():
product.name = form.name.data
product.description = form.description.data
db.session.add(product)
db.session.commit()
return render_template('products/edit.html', form=form, product=product)
The template for the edit form (yumroad/templates/products/edit.html
), looks very similar to the creation form, but uses the edit
route instead. It's useful to have a separate template since we may want to customize what fields we allow on the edit field.
{% block title %} Edit {{ product.name }} {% endblock %}
{% block content %}
<div class="container">
<form method="POST" action="{{ url_for('products.edit', product_id=product.id) }}">
{{ render_field(form.name) }}
{{ render_field(form.description) }}
<button type="submit" class="btn btn-primary">Edit</button>
</form>
</div>
{% endblock %}
CSRF
Our application right now will accept any POST requests and process the change if the user is logged in (by looking at the cookies). This creates a security vulnerability called Cross Site Request Forgery where other websites can create requests on behalf of users who happened to be logged into our site and land on a malicious webpage. It would be bad if we allowed malicious websites the opportunity create products, or worse transfer funds, on behalf of our users.
You can read more about CSRF here.
Setting up CSRF Protection
Flask WTForms allows us to enable CSRF Protection. To support it, we'll need to
In extensions.py
, we can import CSRFProtect
from flask_wtf.csrf
and instantiate it.
extensions.py
should look like this.
from flask_sqlalchemy import SQLAlchemy
from flask_wtf.csrf import CSRFProtect
db = SQLAlchemy()
csrf = CSRFProtect()
In yumroad/__init__.py
, we will have to call csrf.init_app
with our app
to set it up.
yumroad/__init__.py
should look like this:
from flask_sqlalchemy import SQLAlchemy
from flask_wtf.csrf import CSRFProtect
db = SQLAlchemy()
csrf = CSRFProtect()
You will likely also need to set SECRET_KEY
in order to generate CSRF tokens. We will discuss how the SECRET_KEY
is used in the next chapter.
To quickly generate a random value using Python, you can use the os.urandom
method. Run this command in your terminal to get a random string of bytes: python -c 'import os; print(os.urandom(16))'
For the development environment, we can add a simple default value. In yumroad/config.py
, ensure that the DevConfig
specifies a SECRET_KEY
.
class BaseConfig:
TESTING = False
SQLALCHEMY_TRACK_MODIFICATIONS = False
SECRET_KEY = os.getenv('YUMROAD_SECRET_KEY', '00000abcdef')
...
class ProdConfig(BaseConfig):
SECRET_KEY = os.getenv('YUMROAD_SECRET_KEY')
Implementing CSRF Protection in Forms
Implementing CSRF protection is straight form thanks to flask_wtf
. All forms have a csrf_token
field that we need to render in the template. The field is configured to be hidden but the value of the csrf_token
field will be checked through the form validations to ensure that it the provided token is valid.
This lesson preview is part of the Fullstack Flask: Build a Complete SaaS App with Flask course and can be unlocked immediately with a single-time purchase. Already have access to this course? Log in here.
Get unlimited access to Fullstack Flask: Build a Complete SaaS App with Flask with a single-time purchase.