Problem Sending CSRF Token Between React Frontend and Flask Backend

30 views Asked by At

I created a web application that has a Flask Python backend and a React frontend. I've set up CORS so that it accepts requests from my frontend only. I'm using WTF Flask to generate CSRF tokens. Basically before the frontend makes a POST request to the backend, it makes a request to the backend to retrieve a CSRF Token. Then when it sends a POST request the CSRF Token is included in the response headers. The backend verifies the token to check if it matches before it processes the rest of the request.

The issue I'm running into is that I am getting an error where it says incorrect CSRF token. It works in development mode when I set up a proxy in the frontend to be the same port as the backend. But when testing in production mode the CSRF tokens don't match. It might be due to the way Flask manages sessions in the backend? Or the way WTF Flask is generating tokens based on the session....?

Flask backend code snippets: app.py:

from flask_wtf.csrf import generate_csrf, CSRFProtect, CSRFError

@app.route('/get_csrf_token', methods=['POST'])
@csrf.exempt
def get_csrf_token():
    token = generate_csrf()

    # Log the CSRF token to the console
    print("CSRF Token:", token)

    # Log the request headers
    request_headers = dict(request.headers)
    print("Request Headers:", request_headers)
    return jsonify({'csrf_token': token})

@app.errorhandler(CSRFError)
def handle_csrf_error(e):
    return render_template('csrf_error.html', reason=e.description), 400

server.py:

@app.after_request
def after_request(response):
    response.headers.add('Access-Control-Allow-Headers', 'Content-Type,Authorization,X-Csrftoken')
    print("response:", response)
    return response

@app.route("/tracker", methods=["GET", "POST"])
def tracker():
    """Handler for the /racker route"""  
    session_id = request.args.get('session_id')

    csrf_token = request.headers.get('X-Csrftoken')
    print("CSRF Token from Request Headers:", csrf_token)

    # Rest of tracker route code below....

React frontend code snippet:

const handleSubmit = async (event) => {
        // Prevent default form submission behaviour
        event.preventDefault();

        // Fetch the CSRF token from the backend
        const csrfResponse = await fetch(`${backendUrl}/get_csrf_token`, {
            method: 'POST',
        })
        const csrfData = await csrfResponse.json();
        const backendCsrfToken = csrfData.csrf_token;

        console.log("Expected CSRF Token:", backendCsrfToken);

        // Get form data
        const formData = new FormData(event.target);
        formData.append("session_id", sessionID);

        // Make POST request to backend Flask API
        fetch(`${backendUrl}/tracker`, {
            method: 'POST',
            body: formData,
            credentials: 'include',
            headers: {
                'X-CSRFToken': backendCsrfToken,
            },
        })
        .then((response) => response.json())
        .then((data) => {
            console.log(data);
            if (data.error) {
                // Handle the error message received from the backend
                console.error('Error:', data.error);
                // Display the error message to the user
                // Update error message state
                setErrorMessage(data.error);
            } else {
                // Clear error message state
                setErrorMessage('');
                // Fetch updated backend data
                fetchBackendData();
                // Clear form inputs after successful data submission to backend
                event.target.reset();
            }
        })
        .catch((error) => {
            console.error('Error:', error);
        })
    }

If this can't be figured out... I wonder if I need to deploy it differently and set up an nginx reverse proxy or something. Because it works when there is a proxy only.

Details about deployed environment: The backend is deployed as a web service on render.com and the frontend is deployed separately on render.com as a static site.

I've configured CORS on the Flask backend to only accept requests from my frontend after deployment. In my configuration I've allowed these headers:

CORS_ALLOW_HEADERS=["X-Csrftoken", "Authorization", "Content-Type"]
cors_origins = app.config["CORS_ORIGINS"]
cors_supports_credentials = app.config["CORS_SUPPORTS_CREDENTIALS"]
CORS(app, origins=cors_origins, methods=["GET", "POST", "DELETE"], supports_credentials=cors_supports_credentials)
0

There are 0 answers