Reputation: 163
My flask application uses JWT as means of authentication. Those tokens are stored in cookies and flask-jwt-extended is configured to use them. For regular GET requests authentication works fine and @jwt_required
decorator is able to read tokens from cookies and authenticate the user. But when making AJAX POST request using fetch()
the extension is not able to read them and returns Missing CSRF token
error. Strangely when accessing request object in the POST route all required cookies are present as well as in all other routes when authenticated, which means that fetch()
sets all required cookies correctly:
ImmutableMultiDict([
('csrftoken', 'valid_csrf_token'),
('session','valid_session_cookie'),
('access_token_cookie', 'valid_access_token'),
('csrf_access_token', 'valid_csrf_access_token')
])
Flask POST route:
@main.route("/sendmail", methods=["POST"])
@jwt_required()
async def send_mail():
data = json.loads(request.data)
mail_template = render_template("mail-view.html", data=data)
pdf_report = pdfkit.from_string(mail_template, False)
message = Message(
subject="Flask-Mailing module",
recipients=["[email protected]"],
body="Message body",
subtype="html",
)
message.attach("report.pdf", pdf_report)
await mail.send_message(message)
return jsonify({"message": "success"}), 200
Fetch request:
fetch(window.location.origin + "/sendmail", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
credentials: "same-origin",
body: JSON.stringify(mail),
})
My app config object:
class DevConfig:
SECRET_KEY = os.environ.get("SECRET_KEY")
JWT_SECRET_KEY = os.environ.get("JWT_SECRET_KEY")
JWT_COOKIE_SECURE = False
SESSION_COOKIE_SECURE = False
JWT_TOKEN_LOCATION = ["cookies"]
JWT_ACCESS_TOKEN_EXPIRES = datetime.timedelta(hours=1)
MAIL_SERVER = "smtp.googlemail.com"
MAIL_PORT = 587
MAIL_USE_TLS = True
MAIL_USE_SSL = False
MAIL_USERNAME = os.environ.get("MAIL_USERNAME")
MAIL_PASSWORD = os.environ.get("MAIL_PASSWORD")
MAIL_FROM = os.environ.get("MAIL_USERNAME")
Upvotes: 2
Views: 3151
Reputation: 116
Sorry I'm late. Anyway, this might be useful for Flask Template
form.
This main issue for missing csrf access token
may occurs because of the form element on .html
csrf doesn't set. In order to fix can follow this step:
1. Add Form validator on form python (link):
form.py
from wtforms import Form, BooleanField, StringField, PasswordField, validators
class LoginForm(Form):
username = StringField('Username', [validators.DataRequired()])
password = PasswordField('New Password', [validators.DataRequired()])
submit = SubmitField('Submit')
app.py
from flask_restful import Api, Resource, reqparse, marshal, request
from forms import LoginForm
@app.route('/login/', methods=['GET', 'POST'])
def login():
form = LoginForm(request.form)
if request.method == 'POST' and form.validate():
....
return render_template('login.html', title='Login', form=form)
2. Then use Flask CSRF (link) as sample :
<form method="post">
{{ form.csrf_token }}
{{ form.hidden_tag }}
<div>{{ form.username.label }}: {{ form.username }}</div>
<div>{{ form.password.label }}: {{ form.password }}</div>
<div>{{ form.submit.label }}: {{ form.submit }}</div>
</form>
Sample from Docs: https://wtforms.readthedocs.io/en/3.1.x/csrf/#using-csrf
Flask Template form need to use Form validator & CSRF token in order to make it work
Upvotes: 0
Reputation: 634
With flask-jwt-extended default settings the CSRF-Token needs to be send only for state changing request methods (everything except 'GET'). Thats why the 'GET'-method is authorized even without a CSRF-Token. Secondly, the whole idea of a CSRF-Token is to not send it automatically by the web browser, so it is not accepted within a cookie:
By default, we accomplish this by setting two cookies when someone logging in. The first cookie contains the JWT, and encoded in that JWT is the double submit token. This cookie is set as http-only, so that it cannot be access via javascript (this is what prevents XSS attacks from being able to steal the JWT). The second cookie we set contains only the same double submit token, but this time in a cookie that is readable by javascript. Whenever a request is made, it needs to include an X-CSRF-TOKEN header, with the value of the double submit token. If the value in this header does not match the value stored in the JWT, the request is kicked out as invalid.
https://flask-jwt-extended.readthedocs.io/en/stable/token_locations/
So whenever you sent a request you should add the CSRF-Token to the headers:
fetch(window.location.origin + "/sendmail", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRF-TOKEN": getCookie("csrf_access_token"),
},
credentials: "same-origin",
body: JSON.stringify(mail),
})
Upvotes: 5