2025, Dec 28 03:00

Fix Flask sessions disappearing with React on localhost: SameSite cookies, CORS, axios, and proxy tips

Fix Flask session loss between routes when React runs on another port: SameSite cookie settings, CORS, proxy setup, and the session lifetime key fix guide.

Flask session data suddenly vanishing between routes while React sits on another localhost port is a classic trap. Login succeeds, the session looks fine right after you set it, but a follow-up request says there is no session at all. The usual withCredentials flag in axios does not save the day. This guide walks through what is actually going on and how to fix it cleanly for both development and deployment.

Reproducing the issue

The behavior is straightforward: the session is created and visible in the route that sets it, yet it is inaccessible in other routes. Below is a minimal example that mirrors that flow. Names differ, but the logic is identical: authenticate, store user info in session, read it later, fail.

from flask import Flask, request, jsonify, session
from datetime import timedelta
from flask_cors import CORS
svc = Flask(__name__)
svc.secret_key = "HelloWorld"
svc.config["SESSION_PERMANENT"] = True
svc.config["PERMANENT_SESSION_LIFETIMT"] = timedelta(days=1)  # notice the misspelling
CORS(svc, supports_credentials=True, resources={r"/*": {"origins": "http://localhost:5173"}})
@svc.after_request
def allow_cors(resp):
    resp.headers["Access-Control-Allow-Origin"] = "http://localhost:5173"
    resp.headers["Access-Control-Allow-Credentials"] = "true"
    resp.headers["Access-Control-Allow-Headers"] = "Content-Type"
    resp.headers["Access-Control-Allow-Methods"] = "POST, GET, OPTIONS"
    return resp
@svc.route('/auth', methods=['POST'])
def sign_in():
    body = request.get_json()
    mail = body.get("usermail")
    pwd = body.get("password")
    email_stream = user_collection.where("usermail", "==", mail).stream()
    doc = next(email_stream, None)
    if doc is None:
        return jsonify({"message": "User not registered"}), 404
    record = doc.to_dict()
    if record.get("password") != pwd:
        return jsonify({"message": "Invalid credentials"}), 401
    tg_id = record.get("telegramID", "")
    first_time = tg_id.strip() == ""
    session.permanent = True
    session["user_cache"] = record
    session.modified = True
    return jsonify({"message": "Login successful", "first_login": first_time}), 200
@svc.route('/telegram/link', methods=['POST'])
def bind_telegram():
    if 'user_cache' not in session:
        # session not available here in the broken setup
        return jsonify({"message": "Unauthorized"}), 401
    payload = request.get_json()
    acct = session.get('user_cache')
    uid = acct["user_id"] if acct else None
    tg_val = payload.get("telegramID")
    if not tg_val:
        return jsonify({"message": "Telegram ID is required"}), 400
    q = user_collection.where("user_id", "==", uid).stream()
    found = next(q, None)
    if found is None:
        return jsonify({"message": "User not found"}), 404
    user_collection.document(found.id).update({"telegramID": tg_val})
    return jsonify({"message": "Telegram ID added successfully"}), 200
@svc.route('/probe')
def probe():
    acct = session.get('user_cache')
    if acct:
        return acct
    return jsonify({"message": "No data"})
if __name__ == '__main__':
    svc.run(debug=True)

What is actually happening

Two independent culprits can produce exactly this symptom. The first is a simple configuration typo: the intended session lifetime setting is not applied because the key name is misspelled. The second is the Same Site Policy. When your React app runs at http://localhost:5173 and your Flask API at http://localhost:5000, the browser treats them as different sites. In such a setup, the browser may reject or not send the session cookie to the backend unless you either make both origins effectively the same site during development or explicitly allow third-party cookies for your session.

In practice, this means the cookie that carries your Flask session does not make it to the next route, so the backend sees an empty session and responds with “Unauthorized” or “No data”.

How to fix it

First, correct the configuration key so your session lifetime is actually set. Then decide whether you want to avoid cross-site cookies in development by using a proxy, or you want to allow third-party cookies explicitly.

To enable a same-site development setup with Create React App, add a proxy to the frontend. Requests go through the frontend dev server and are forwarded to Flask, eliminating CORS and third-party cookie friction because the browser sees one site.

"proxy": "http://localhost:5000"

If your tooling is Vite, configure the proxy in vite.config.js accordingly.

If you later deploy frontend and backend without a shared proxy, use third-party cookies by setting the cookie attributes that permit cross-site usage.

svc.config["SESSION_COOKIE_SAMESITE"] = "None"
svc.config["SESSION_COOKIE_SECURE"] = True

Here is a corrected Flask bootstrap that applies both the fixed lifetime key and the cookie attributes for the cross-site scenario, while keeping the same application logic as above.

from flask import Flask
from datetime import timedelta
from flask_cors import CORS
svc = Flask(__name__)
svc.secret_key = "HelloWorld"
svc.config["SESSION_PERMANENT"] = True
svc.config["PERMANENT_SESSION_LIFETIME"] = timedelta(days=1)  # fixed key
# Enable third-party cookie usage when frontend and backend are on different origins
svc.config["SESSION_COOKIE_SAMESITE"] = "None"
svc.config["SESSION_COOKIE_SECURE"] = True
CORS(svc, supports_credentials=True, resources={r"/*": {"origins": "http://localhost:5173"}})

Why this matters

Sessions are cookie-backed in this setup. If the cookie is not sent, the backend cannot correlate requests, regardless of what you print right after setting the session. Understanding when the browser considers two origins to be the same site, and how Same Site Policy treats cookies, saves hours of debugging. It also prevents brittle workarounds and makes the path to production clearer.

Practical takeaways

Make sure your session settings are spelled correctly so your intended lifetime takes effect. During development, prefer a proxy so the frontend and backend look like one site. If you must run them on separate origins, explicitly configure cookies for third-party usage. For deeper background, the MDN documentation on same-origin policy and third-party cookies is an excellent reference. Tool-specific guidance for proxies is available for Create React App and Vite.

In short, fix the misnamed lifetime key, choose a proxy for a frictionless local setup, or set the cookie attributes for cross-site behavior when that is your deployment model. With that in place, the session you set in one route will be available in the next.