flask-security
flask-security copied to clipboard
CSRF protection doesn't turn off on token requests
Hey,
I am trying to disable CSRF protection for token-based access. But I can't get it to work. The form doesn't validate because of {'csrf_token': ['The CSRF token is missing.']}
. I am not sure if this is a problem with Flask Security or Flask WTF. So I made a minimal example:
import os
from flask import Flask, render_template_string
from flask_wtf import CSRFProtect, FlaskForm
from wtforms.fields import StringField
from flask_sqlalchemy import SQLAlchemy
from flask_security import (
Security,
SQLAlchemyUserDatastore,
auth_required,
hash_password,
)
from flask_security.models import fsqla_v3 as fsqla
# Create app
app = Flask(__name__)
app.config["DEBUG"] = True
app.config["SECRET_KEY"] = "This is no secret key"
# Bcrypt is set as default SECURITY_PASSWORD_HASH, which requires a salt
# Generate a good salt using: secrets.SystemRandom().getrandbits(128)
app.config["SECURITY_PASSWORD_SALT"] = "This is not my salt"
# have session and remember cookie be samesite (flask/flask_login)
app.config["REMEMBER_COOKIE_SAMESITE"] = "strict"
app.config["SESSION_COOKIE_SAMESITE"] = "strict"
# Use an in-memory db
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite://"
# As of Flask-SQLAlchemy 2.4.0 it is easy to pass in options directly to the
# underlying engine. This option makes sure that DB connections from the
# pool are still valid. Important for entire application since
# many DBaaS options automatically close idle connections.
app.config["SQLALCHEMY_ENGINE_OPTIONS"] = {
"pool_pre_ping": True,
}
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
# Create database connection object
db = SQLAlchemy(app)
# Define models
fsqla.FsModels.set_db_info(db)
class Role(db.Model, fsqla.FsRoleMixin):
pass
class User(db.Model, fsqla.FsUserMixin):
pass
# Disable pre-request CSRF
app.config["WTF_CSRF_CHECK_DEFAULT"] = False
# Don't check for CSRF in token and session based requests
app.config["SECURITY_CSRF_PROTECT_MECHANISMS"] = ["basic"]
# Enable CSRF protection
CSRFProtect(app)
# Setup Flask-Security
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
app.security = Security(app, user_datastore)
class MyForm(FlaskForm):
name = StringField("name")
@app.route("/")
@auth_required("token", "session")
def home_page():
return render_template_string(
"""
<html>
<body>
Your Auth Token: <pre>{{ current_user.get_auth_token() }}</pre>
<form action="/" method="post">
<input type="text" name="name" value="Your name "/>
<input type="submit" value="OK" />
</form>
</body>
</html>
"""
)
@app.route("/", methods=["POST"])
@auth_required("token", "session")
def post_page():
form = MyForm()
if form.validate_on_submit():
return render_template_string("Successfully posted.")
return render_template_string(f"Wasn't successful: {form.errors}")
# one time setup
with app.app_context():
# Create User to test with
db.create_all()
if not app.security.datastore.find_user(email="[email protected]"):
app.security.datastore.create_user(
email="[email protected]", password=hash_password("password")
)
db.session.commit()
if __name__ == "__main__":
app.run()
You will get the "CSRF token missing" error for both token and session based auth. But if I understand the documentation correctly, it shouldn't be checked at all. As far as I can see, the CSRF error happens on form.validate_on_submit
. I was able to fix this by modifying Flask Security handle_csrf
function. I added g.csrf_valid = True
for all methods not in CSRF_PROTECT_MECHANISMS
and the CSRF token isn't checked anymore (but I’m not sure this is a proper solution for my problem).
Can you help me to understand if this is a problem with Flask Security, Flask WTF, or me?
Definitely an issue - the mechanism that Flask Secrurity uses to bypass CSRF in this case is built into our forms - not available to external forms. Let me look at that.