miguelgrinberg/microblog

Hi miguel! Thankyou for your tutorial.

Closed this issue · 10 comments

I tried implementing a flask app on my own and im facing an error. Here's the code:
app.py:

from flask_sqlalchemy import SQLAlchemy
from flask_cors import CORS
from flask_wtf import FlaskForm, CSRFProtect
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, Email, EqualTo, ValidationError
import os

app = Flask(__name__)
csrf = CSRFProtect(app)
CORS(app)
app.config['SECRET_KEY'] = os.getenv('SECRET_KEY')
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users.db'
# app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)

# User model
class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    first_name = db.Column(db.String(50), nullable=False)
    last_name = db.Column(db.String(50), nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    password = db.Column(db.String(120), nullable=False)
    ip = db.Column(db.String(45), nullable=False)
    created_at = db.Column(db.DateTime, default=db.func.current_timestamp())

# Create the database and the table
with app.app_context():
    db.create_all()

class SignupForm(FlaskForm):
    first_name = StringField('First Name', validators=[DataRequired()])
    last_name = StringField('Last Name', validators=[DataRequired()])
    email = StringField('Email', validators=[DataRequired(), Email()])
    password = PasswordField('Password', validators=[DataRequired()])
    confirm_password = PasswordField('Confirm Password', validators=[DataRequired(), EqualTo('password')])
    submit = SubmitField('Sign Up')

@app.route('/api/signup', methods=['POST'])
def signup():
    form = SignupForm(data=request.json)
    if form.validate():
        if User.query.filter_by(email=form.email.data).first():
            return jsonify({'message': 'Email already exists.', 'success': False}), 400

        user_ip = request.remote_addr

        new_user = User(
            first_name=form.first_name.data,
            last_name=form.last_name.data,
            email=form.email.data,
            password=form.password.data,
            ip=user_ip
        )
        db.session.add(new_user)
        db.session.commit()

        return jsonify({'message': 'User signed up successfully!', 'success': True}), 200
    else:
        app.logger.debug(f"Form errors: {form.errors}")
        return jsonify({'message': 'Form validation failed.', 'errors': form.errors, 'success': False}), 400

@app.route('/api/users', methods=['GET'])
def get_users():
    users = User.query.all()
    user_list = [{'first_name': user.first_name, 'last_name': user.last_name, 'email': user.email, 'created_at': user.created_at} for user in users]
    return jsonify(user_list)

if __name__ == '__main__':
    app.run(port=5000, debug=True)

script.js:

const signupForm = document.getElementById('signupForm');
if (signupForm) {
    signupForm.addEventListener('submit', async (e) => {
        e.preventDefault();

        const formData = new FormData(signupForm);
        const data = {};
        formData.forEach((value, key) => {
            data[key] = value;
        });

        console.log('Data being sent:', data); // Log the data being sent
        try {
            const response = await fetch('http://localhost:5000/api/signup', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify(data),
            });
        
            const result = await response.json();
            document.getElementById('message').textContent = result.message;
        } catch (error) {
            console.error('An error occurred:', error); // Add this line to log the error
            document.getElementById('message').textContent = 'An error occurred. Please try again.';
        }
        
    });
}

document.addEventListener('DOMContentLoaded', async () => {
    const userList = document.getElementById('userList');

    try {
        const response = await fetch('http://localhost:5000/api/users');
        const users = await response.json();

        users.forEach(user => {
            const row = document.createElement('tr');
            const emailCell = document.createElement('td');
            const dateCell = document.createElement('td');

            emailCell.textContent = user.email;
            dateCell.textContent = new Date(user.created_at).toLocaleString();

            row.appendChild(emailCell);
            row.appendChild(dateCell);
            userList.appendChild(row);
        });
    } catch (error) {
        console.error('Error fetching user data:', error);
    }
});

Earlier I didn't include csrf in my project, when i did, i faced a new error, and this wont work.
the error is:
script.js:24 An error occurred: SyntaxError: Unexpected token '<', "<!doctype "... is not valid JSON
When i open the network tab and see the preview, i see an html render saying

Bad Request
The CSRF token is missing.

Why so? I'm a beginner and im confused.

@Kishlay-notabot you have enabled CSRF protection for your Flask server, but then in the client you are not sending a CSRF token.

Thankyou for pointing that out. Do flask wtf forms require csrf tokens generally ?

Yes, for forms it is a good practice to use CSRF protection. But you have enabled it for the entire server.

Oh okay, how do I limit csrf to just the scope of the form?

By creating a seperate forms.py file?

Remove this:

csrf = CSRFProtect(app)

when i try to run the app after removing that, i encounter this error again:
[2024-06-16 17:07:19,427] DEBUG in app: Form errors: {'csrf_token': ['The CSRF token is missing.']}
and this was the main reason i started playing around with csrf. How do i bypass this then?
And this pops up when i fill the form and click submit, There is an error 400 bad request on the console.

Ok so maybe the code I'm running is a mess, if you could just point out where is it going wrong, it would be very nice because I want to learn where did I actually go wrong. Maybe I'll not use flask-wtf for now, but I want to know where the mistake is exactly

Forms are configured to require CSRF by default, you don't have to add anything to your application. How do you submit your forms?

  • If you submit your forms through standard GET or POST requests, follow these instructions to add the CSRF token to your HTML forms.
  • If you submit your forms as asynchronous requests with JavaScript, follow these instructions instead.

thanks a lot for helping! ill check the resources out