Flask-WTF show error message: "The CSRF tokens do not match." - javascript

This the error message I got when I submit empty fields (which is behave like I want in my class form validators):
But when I try to submit with valid email, it give me this error message:
I basically want to submit a form using Flask-WTF but handling it from Javascript fetch. Because I want to perform it dynamically like New York Times Login Form: NYT Login Form
App.py
import os
from flask import Flask, jsonify, flash, redirect, render_template, request, session
from flask_session import Session
from flask_api import status
from flask_sqlalchemy import SQLAlchemy
from flask_wtf import FlaskForm
from wtforms import StringField
from wtforms.validators import InputRequired
from flask_wtf.csrf import CSRFProtect
from werkzeug.security import check_password_hash, generate_password_hash
from helpers import login_required
# Configure flask application
app = Flask(__name__)
# Configure Secret Key
app.config["SECRET_KEY"] = "secret123"
# Create Form Class
class AuthenticationForm(FlaskForm):
email = StringField('Email Address', [InputRequired(message="Please enter your email address")])
# Ensure templates are auto-reloaded
app.config["TEMPLATES_AUTO_RELOAD"] = True
# Configure session to use filesystem (instead of signed cookies)
app.config["SESSION_PERMANENT"] = False
app.config["SESSION_TYPE"] = "filesystem"
Session(app)
# Configure SQLAlchemy databases
basedir = os.path.abspath(os.path.dirname(__file__))
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///" + os.path.join(basedir, 'newspaper.db')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
# Create SQLAlchemy object of class
class Readers(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
email = db.Column(db.Text, unique=True, nullable=False)
hash = db.Column(db.Text, nullable=False)
def __repr__(self):
return f'<Readers {self.email}>'
#app.after_request
def after_request(response):
"""Ensure responses aren't cached"""
response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
response.headers["Expires"] = 0
response.headers["Pragma"] = "no-cache"
return response
#app.route("/")
#login_required
def index():
"""Show Newspaper"""
return redirect('/auth/login')
#app.route("/auth/<authentication>", methods=["GET", "POST"])
def auth(authentication):
"""Log user in"""
# Forget any user_id
session.clear()
# Get form class
form = AuthenticationForm()
# User reached route via POST (as by submitting a form via POST)
if request.method == "POST":
if authentication == 'login':
if form.validate():
data = request.get_json()
email = data["email"]
# Check if email already registered in database
user = Readers.query.filter_by(email=email).first()
if not user:
return "Not Registered Yet", 200
return "Valid!", 200
return jsonify(form.errors), 400
# User reached route via GET (as by clicking a link or via redirect)
return render_template("auth.html", form=form)
auth.html
{% extends "layout.html" %}
{% block title %}
Login -
{% endblock %}
{% block main %}
<div class="auth__form-wrapper">
<div class="auth__title">
<h2>Log in or create an account</h2>
</div>
<div id="success-message"></div>
<form id="form" method="post">
{{ form.csrf_token }}
<fieldset type="email" class="mb-1">
{{ form.email.label }}
{{ form.email(autocomplete="off", autofocus=true, required=false) }}
</fieldset>
<div id="error-message">
<span style="color: #cf0000;" id="error"></span>
</div>
<button id="continue" class="btn btn-dark">Continue</button>
</form>
</div>
<script src="{{url_for('static', filename='authentication.js')}}"></script>
{% endblock %}
.js file to handling submit:
const form = document.querySelector('#form');
const successMessage = document.querySelector('#success-message');
const errorMessage = document.querySelector('#error');
const fields = {
csrf_token: {
input: document.querySelector('#csrf_token')
},
email: {
input: document.querySelector('#email')
}
}
form.addEventListener('submit', async (e) => {
e.preventDefault();
const response = await fetch('/auth/login', {
'method': 'POST',
'headers': {
'Content-Type': 'application/json'
},
'body': JSON.stringify({
'csrf_token': fields.csrf_token.input.value,
'email': fields.email.input.value
})
});
if (response.ok) {
successMessage.innerHTML = await response.text();
} else {
const err = await response.json();
Object.keys(err).forEach((key) => {
errorMessage.innerHTML = err[key][0];
});
}
})
If I just use a traditional way instead of Javascript fetch to validate Flask-WTForm using validate_on_submit() it perform good. Because validate_on_submit() seems like handle all validators.
But I just don't know why this happen. Should I use Javascript to handle Flask WTF like my code above? Or I just use Flask WTF using validate_on_submit()? I just want to make a login form that behave like New York Times Login Form: NYT Login Form
I'm still new on this. I hope you guys can help.

Related

Flask-WTForms give errors message: "The CSRF tokens do not match."

I thought I was finally succeed until this csrf_token errors is showed.
My goal is to validate form using Flask-WTF but handle it with Fetch Javascript (instead of let the validate_on_submit function do the job). Because I want to perform it dynamically.
It will show errors between input field and submit button if it's failed to validate. And if it's success it will show the message in other element innerHTML.
It work when I submit with empty input. It will show the message like I want "Please enter your email address". But when I type an valid email, it still show the error message which The CSRF tokens do not match.
App.py
import os
from flask import Flask, jsonify, flash, redirect, render_template, request, session
from flask_session import Session
from flask_api import status
from flask_sqlalchemy import SQLAlchemy
from flask_wtf import FlaskForm
from wtforms import StringField
from wtforms.validators import InputRequired
# Configure flask application
app = Flask(__name__)
# Configure Secret Key
app.config["SECRET_KEY"] = "secret123"
# Create Form Class
class AuthenticationForm(FlaskForm):
email = StringField('Email Address', [InputRequired(message="Please enter your email address")])
# Ensure templates are auto-reloaded
app.config["TEMPLATES_AUTO_RELOAD"] = True
# Configure session to use filesystem (instead of signed cookies)
app.config["SESSION_PERMANENT"] = False
app.config["SESSION_TYPE"] = "filesystem"
Session(app)
# Configure SQLAlchemy databases
basedir = os.path.abspath(os.path.dirname(__file__))
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///" + os.path.join(basedir, 'newspaper.db')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
# Create SQLAlchemy object of class
class Readers(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
email = db.Column(db.Text, unique=True, nullable=False)
hash = db.Column(db.Text, nullable=False)
def __repr__(self):
return f'<Readers {self.email}>'
#app.after_request
def after_request(response):
"""Ensure responses aren't cached"""
response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
response.headers["Expires"] = 0
response.headers["Pragma"] = "no-cache"
return response
#app.route("/")
def index():
"""Show Newspaper"""
return redirect('/auth/login')
#app.route("/auth/<authentication>", methods=["GET", "POST"])
def auth(authentication):
"""Log user in"""
# Forget any user_id
session.clear()
# Get form class
form = AuthenticationForm()
# User reached route via POST (as by submitting a form via POST)
if request.method == "POST":
if authentication == 'login':
if form.validate():
data = request.get_json()
email = data["email"]
# Check if email already registered in database
user = Readers.query.filter_by(email=email).first()
if not user:
return "Not Registered Yet", 200
return "Valid!", 200
return jsonify(form.errors), 400
# User reached route via GET (as by clicking a link or via redirect)
return render_template("auth.html", form=form)
auth.html
{% extends "layout.html" %}
{% block title %}
Login -
{% endblock %}
{% block main %}
<div class="auth__form-wrapper">
<div class="auth__title">
<h2>Log in or create an account</h2>
</div>
<div id="success-message"></div>
<form id="form" method="post">
{{ form.csrf_token }}
<fieldset type="email" class="mb-1">
{{ form.email.label }}
{{ form.email(autocomplete="off", autofocus=true, required=false) }}
</fieldset>
<div id="error-message">
<span style="color: red;" id="error"></span>
</div>
<button id="continue" class="btn btn-dark">Continue</button>
</form>
</div>
<script src="{{url_for('static', filename='authentication.js')}}"></script>
{% endblock %}
authentication.js (file to handle form submit)
const form = document.querySelector('#form');
const successMessage = document.querySelector('#success-message');
const errorMessage = document.querySelector('#error');
const fields = {
csrf_token: {
input: document.querySelector('#csrf_token')
},
email: {
input: document.querySelector('#email')
}
}
form.addEventListener('submit', async (e) => {
e.preventDefault();
const response = await fetch('/auth/login', {
'method': 'POST',
'headers': {
'Content-Type': 'application/json'
},
'body': JSON.stringify({
'csrf_token': fields.csrf_token.input.value,
'email': fields.email.input.value
})
});
if (response.ok) {
successMessage.innerHTML = await response.text();
} else {
const err = await response.json();
Object.keys(err).forEach((key) => {
errorMessage.innerHTML = err[key][0];
});
}
})
When I submit with empty input field:
When I submit with valid email:
I don't understand. I hope you guys can help. I just want to create a Form that behave like The New york times: NYTForm Login

Flask WTForms keep giving error message: "The CSRF_TOKEN is missing." when handling the form submit with JavaScript Fetch

I'm totally confused with this Flask WTForms.
What I want to do is simple. I want to validate a Form by handling it's submit with JavaScript Fetch. Because I want to make it dynamic in front-side. Just like a modern website form, it will show the red message if we submit invalid input, and another success message if we submit valid input.
But in this case, I feel like I've succeed to show the error message if user put empty email in input field. It will show the error message like I want in my Class Form validators. This is the image:
But the problem is, when I typed valid email address it will give meaningless error "The CSRF Token is missing." Like in this image:
Now this is my code:
App.py
import os
from flask import Flask, jsonify, flash, redirect, render_template, request, session
from flask_session import Session
from flask_api import status
from flask_sqlalchemy import SQLAlchemy
from flask_wtf import FlaskForm
from wtforms import StringField
from wtforms.validators import InputRequired
from flask_wtf.csrf import CSRFProtect
from werkzeug.security import check_password_hash, generate_password_hash
from helpers import login_required
# Configure flask application
app = Flask(__name__)
# Configure CSRF Token for WTForm
app.config["WTF_CSRF_ENABLED"] = True
app.config["SECRET_KEY"] = "secret123"
DEBUG = True
# Create WTForm Class
class AuthenticationForm(FlaskForm):
email = StringField('Email Address', [InputRequired(message="Please enter your email address")])
# Ensure template is reloaded
app.config["TEMPLATES_AUTO_RELOAD"] = True
# Configure session to use filesystem (instead of signed cookies)
app.config["SESSION_PERMANENT"] = False
app.config["SESSION_TYPE"] = "filesystem"
Session(app)
# Configure SQLAlchemy databases
basedir = os.path.abspath(os.path.dirname(__file__))
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///" + os.path.join(basedir, 'newspaper.db')
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db = SQLAlchemy(app)
# Create SQLAlchemy object of class
class Readers(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
email = db.Column(db.Text, unique=True, nullable=False)
hash = db.Column(db.Text, nullable=False)
def __repr__(self):
return f'<Readers {self.email}>'
#app.after_request
def after_request(response):
"""Ensure responses aren't cached"""
response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
response.headers["Expires"] = 0
response.headers["Pragma"] = "no-cache"
return response
#app.route("/")
#login_required
def index():
"""Show Newspaper"""
return redirect('/auth/login')
#app.route("/auth/<authentication>", methods=["GET", "POST"])
def auth(authentication):
"""Log user in"""
# Forget any user_id
session.clear()
# Get form class
form = AuthenticationForm()
# User reached route via POST (as by submitting a form via POST)
if request.method == "POST":
if authentication == 'login':
if form.validate():
data = request.get_json()
email = data["email"]
# Check if email already registered in database
user = Readers.query.filter_by(email=email).first()
if not user:
return jsonify({"email":"Not Registered Yet"}), 200
return jsonify({"email":"Registered"}), 200
return jsonify(form.errors), 400
# User reached route via GET (as by clicking a link or via redirect)
return render_template("auth.html", form=form)
auth.html (templates):
{% extends "layout.html" %}
{% block title %}
Login -
{% endblock %}
{% block main %}
<div class="auth__form-wrapper">
<div class="auth__title">
<h2>Log in or create an account</h2>
</div>
<div id="success-message"></div>
<form id="form" method="post">
{{ form.csrf_token }}
<fieldset type="email" class="mb-1">
{{ form.email.label }}
{{ form.email(autocomplete="off", autofocus=true, required=false) }}
</fieldset>
<div id="error-message">
<span style="color: #cf0000;" id="error"></span>
</div>
<button id="continue" class="btn btn-dark">Continue</button>
</form>
</div>
<script src="{{url_for('static', filename='authentication.js')}}"></script>
{% endblock %}
.js file to handle form submit with fetch:
const form = document.querySelector('#form');
const successMessage = document.querySelector('#success-message');
const errorMessage = document.querySelector('#error');
const fields = {
csrf_token: {
input: document.querySelector('#csrf_token')
},
email: {
input: document.querySelector('#email')
}
}
form.addEventListener('submit', async (e) => {
e.preventDefault();
const response = await fetch('/auth/login', {
'method': 'POST',
'headers': {
"Content-type": "application/json",
'csrf_token': fields.csrf_token.input.value
},
'body': JSON.stringify({
'email': fields.email.input.value
})
});
if (response.ok) {
const success = await response.json();
successMessage.innerHTML = success["email"]
} else {
const err = await response.json();
Object.keys(err).forEach((key) => {
errorMessage.innerHTML = err[key][0];
});
console.log(err)
}
})
I've tried many solutions that I found. But it didn't solve the problem. One of the solution I found from internet it's put CSRFProtect() in my app.py.
# Configure CSRF Token for WTForm
CSRFProtect(app)
app.config["WTF_CSRF_ENABLED"] = True
app.config["SECRET_KEY"] = "secret123"
DEBUG = True
But instead it gives me new error in console:
Uncaught (in promise) SyntaxError: Unexpected token '<', "<!doctype "... is not valid JSON
I don't know what to do. I hope you guys know what's wrong with this. Or maybe there is another way I could do to validate this Form dynamically. Thank you.

How can I add a new to-do item to my Ajax web app?

I am currently undertaking the Udacity Full Stack developer "degree" and it seems that the lesson on Ajax is incorrect (the code presented on the lesson does not work) and that is causing trouble to all students who take the course. Their help portal is filled with people questioning why the code wont work and it's been over 1 year and Udacity has not provided an answer or corrected the material.
I am hoping to find an answer here to help all of the community.
The task is a simple to-do web app with a postgresql database behind it. Please see below for the code and view:
from xmlrpc.client import Boolean
from flask import Flask, render_template, request, redirect, url_for, jsonify
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://login:abc#localhost:5432/todoapp'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
migrate = Migrate(app, db)
class Todo(db.Model):
__tablename__='todos'
id = db.Column(db.Integer, primary_key=True)
description = db.Column(db.String(), nullable=False)
completed = db.Column(db.Boolean, nullable=False, default=False)
def __repr__(self):
return f'<todo {self.id} {self.description}>'
#app.route('/todos/create', methods=['POST'])
def create_todo():
description = request.get_json()['description']
todo = Todo(description=description)
db.session.add(todo)
db.session.commit()
return jsonify({
'description': todo.description
})
#app.route('/')
def index():
return render_template('index.html', data=Todo.query.all())
# FLASK-SETUP This code should be at the bottom of all your files.
if __name__ == '__main__':
app.debug = True
app.run(host='0.0.0.0', port=3000)
The view related to the above code is:
<html>
<head>
<title>Todo App</title>
<style>
.hidden {
display: none;
}
</style>
</head>
<body>
<form>
<input type="text" id="description" name="description" />
<input type="submit" value="Create" />
</form>
<div id="error" class="hidden">Something went wrong!</div>
<ul>
{% for d in data %}
<li>{{ d.description }}</li>
{% endfor %}
</ul>
<script>
document.getElementById('form').onsubmit = function(e) {
e.preventDefault();
fetch('/todos/create', {
method: 'POST',
body: JSON.stringify({
'description': document.getElementById('description').value
}),
headers: {
'Content-Type': 'application/json'
}
})
.then(function(response) {
return response.json();
})
.then(function(jsonResponse) {
console.log(jsonResponse);
const liItem = document.createElement('LI');
liItem.innerHTML = jsonResponse['description'];
document.getElementById('todos').appendChild(liItem);
document.getElementById('error').classname = 'hidden';
})
.catch(function() {
document.getElementById('error').classname = '';
})
}
</script>
</body>
</html>
When I add a new item to the todo list and hit the create button, the item is not added to the database and the page isn't updated. All that happens is the URL changes to http://127.0.0.1:3000//?description=new+item and the terminal shows 127.0.0.1 - - [02/Mar/2022 00:37:57] "GET /?description=new+item HTTP/1.1" 200 -. The item is not added to the database or displayed on the page.
Can anyone see what the issue is? Any assistance would be hugely helpful to me but also to hundreds of Udacity students who can't count on Udacity.

How do you get the "user_id" of the token payload into the JSON POST form?Django REST, simpleJWT, Vue3

So i have access and refresh tokens in my local storage. The decoded access token has "user_id" in the payload. I have a problem understanding how do you make a POST request to the REST API, where the JSON form includes the "user_id" of the currently logged in user. Do i get it from the stored JWT or is there another way?
For a valid POST request i need 3 fields:
{
"post_author": "field, which needs the currently logged in user, aka user_id from the token "
"post_body": "some text"
"post_title": "some text"
}
Simplified question, how do you get the "user_id" of the decoded token into the JSON form?
createPost.vue
<template>
<div id="authenticationDiv">
<div>
<input type="text" v-model="postTitle" placeholder="post_title" />
<input type="text" v-model="postBody" placeholder="post_body" />
</div>
<br />
<button #click="createPost">Submit Post</button>
</div>
</template>
<script>
import { getPosts } from "./importable_functions";
import { ref } from "vue"
export default {
setup() {
const ACCESS_TOKEN = "access_token";
const postTitle = ref("");
const postBody = ref("");
async function createPost() {
// var csrftoken = getCookie("csrftoken");
fetch("http://127.0.0.1:8000/api/create_post/", {
method: "POST",
headers: {
"Content-type": "application/json",
// "X-CSRFToken": csrftoken,
Authorization: `Bearer ${window.localStorage.getItem(ACCESS_TOKEN)}`,
},
body: JSON.stringify({
post_body: postBody.value,
post_title: postTitle.value,
// post_author:
}),
}).then((response) => {
getPosts();
return response;
});
}
return {
postTitle,
postBody,
createPost,
};
},
};
</script>
views.py create_post view
#api_view(['POST'])
def create_post(request):
serializer = PostSerializer(data=request.data)
if serializer.is_valid():
serializer.save(post_author=request.user)
return Response(serializer.data)
serializers.py
class PostSerializer(serializers.ModelSerializer):
post_author_username = serializers.ReadOnlyField(source="post_author.username")
post_author = serializers.ReadOnlyField(source="post_author")
class Meta:
model = Post
fields = '__all__'
models.py - post and customUser models
class Post(models.Model):
post_author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='posts')
post_title = models.CharField(max_length=200)
post_body = models.TextField()
created_date = models.DateTimeField(default=timezone.now)
published_date = models.DateTimeField(blank=True, null=True)
def __str__(self):
return self.post_title
class CustomUser(AbstractUser):
fav_color = models.CharField(blank=True, max_length=120)
You can send post_title and post_body in the JSON and add user id during save:
#api_view(['POST'])
def create_post(request):
serializer = PostSerializer(data=request.data)
if serializer.is_valid():
serializer.save(post_author=request.user) # add post author
return Response(serializer.data)
Please remember to update serializer, I think it will be good to set post_author as read-only field.
class PostSerializer(serializers.ModelSerializer):
post_author_username = serializers.ReadOnlyField(source="post_author.username")
class Meta:
model = Post
read_only_fields = (post_author_username, post_author)
fields = (post_author_username, post_author, post_title, post_body)

Django How to add data to database from javascript

I am have created a page in django, on this page I have create a button that calls a JavaScript function which in turn gets data from a API. This part of my code works as expected as it writes the response data to the console. However I cannot seem to get that data to be inserted into the model I have created in django.
I am not sure how python/javascript/models are meant to all link together.
models.py
from django.db import models
class Set(models.Model):
scry_id = models.CharField(max_length=255)
code = models.CharField(max_length=255)
name = models.CharField(max_length=255)
set_type = models.CharField(max_length=255)
release_date = models.DateField()
card_count = models.IntegerField()
block_code = models.CharField(max_length=255, null=True)
block_name = models.CharField(max_length=255, null=True)
parent_set_code = models.CharField(max_length=255, null=True)
digital_only = models.BooleanField(default=False)
foil_only = models.BooleanField(default=False)
nonfoil_only = models.BooleanField(default=False)
icon = models.CharField(max_length=255)
status = models.BooleanField(default=False)
def __str__(self):
return self.name
sets.html
{% extends "main/index.html "%}
{% block content %}
<div class="background card">
<div class="card-body">
<button class="btn" id="setRefresh" style="border: 1px solid" onclick="setRefresh()"><i class="fas fa-sync"></i></button>
</div>
</div>
{% endblock%}
custom.js
function setRefresh() {
const Url="https://api.scryfall.com/sets";
fetch(Url)
.then(res => res.json())
.then(data => obj = data.data)
.then(() => obj.sort(function(a,b){return a.released_at.localeCompare(b.released_at);}))
.then(() => {
for (var i = 0; i < obj.length; i++) {
//console.log(obj[i].name);
}
})
}
view.py
def sets(request):
return render(request,
"main/sets.html",
{"Sets": Set.objects.all})
There are two missing parts. First you need to have a url to listen for changes and then you need to have a view function where you want to set data. And you need to make some changes for the JS part of your code.Example below can clear this up and it is functional as well:
views.py
#ajax_required
def views_name(request):
try:
if request.method == 'POST':
post_id = request.POST.get('post')
YourModel.objects.create(id=post_id)
except Exception: # pragma: no cover
return HttpResponseBadRequest()
urls.py
urlpatterns = [
url(r'^whatever/$', views.views_name, name='whatever'),
]
custom.js:
$(function () {
$(".class-name").click(function () {
var csrf = $(this).attr('csrf');
var post = $(this).attr('page-id');
$.ajax({
url: '/whatever/',
data: {
'post': post,
'csrfmiddlewaretoken': csrf
},
type: 'post',
cache: false,
success: function (returned_values) {
// do whatever you want after success!
},
});
});
})
There are two ways to do it
Method 1 : After retrieving the data, send them to your django app instead of logging them into the console . have a django view that handles the corresponding request coming from your js code containing the data then adding them to the db. In other words you should fetch again , but this time to your django app using a post request .
.Your view should look like this :
from .models import Set
from django.http import HttpResponse
import json
def save(request):
data=json.loads(request.body)
for entry in data:
s = Set()
s.scry_id=entry["scry_id"]
#in the next lines you map entry fields to Set fields
s.save()
return HttpResponse("ok")
Method 2 : Your button call your django app on click , then your django app retrieve the data from https://api.scryfall.com/sets itself then save them to the db. Your code should look like this
from .models import Set
from django.http import HttpResponse
import json
import requests
def save(request):
response = requests.request("GET", "https://api.scryfall.com/sets")
data=response.json()
for entry in data:
s = Set()
s.scry_id=entry["scry_id"]
#in the next lines you map entry fields to Set fields
s.save()
return HttpResponse("ok")
Of course in either case don't forget to map your urlconf to the save view

Categories