Cookies from Flask server not persisting in browser

I can’t seem to get my cookies to persist on the browser. I want this cookie to persist so then subsequent requests to my API backend will be authenticated.

I have a React frontend being served on port 8080 through a dev server (Vite) and a Flask API backend on port 5000.

My Flask app is using flask-cors and flask-jwt-extended. With flask-jwt-extended, the settings are mostly default except for these values

JWT_TOKEN_LOCATION="cookies"
JWT_COOKIE_SECURE="false"
JWT_COOKIE_DOMAIN="127.0.0.1"
JWT_CSRF_IN_COOKIES="true"

With flask-cors I have origins set to 127.0.0.1:8080

With these settings set, React is making a fetch that looks like this

const response = await fetch(
    "http://127.0.0.1:5000/api/authentication/login",
    {
      method: "POST",
      headers: {
        credentials: "include",
        "Content-Type": "application/json",
      },
      body: JSON.stringify({email, username}),
    }
  );

The Flask backend handles the request with this endpoint

def login() -> Response:
    email = request.json.get("email", None)
    password = request.json.get("password", None)
    if email is None or password is None:
        return jsonify({"error": "error!"}), 400
    tokens = Authentication.login(email=email, password=password)
    access_token, refresh_token = (
        tokens["access_token"],
        tokens["refresh_token"],
    )
    response = jsonify(access_token=access_token, refresh_token=refresh_token)
    set_access_cookies(response=response, encoded_access_token=access_token)
    set_refresh_cookies(response=response, encoded_refresh_token=refresh_token)
    return response

The response is a 200 with my access_token and refresh_token in the response body (expected) and the response headers below.

Access-Control-Allow-Credentials:    true
Access-Control-Allow-Origin:    http://127.0.0.1:8080
Connection:    close

Content-Length:    792
Content-Type:    application/json
Date:    Tue, 02 Jan 2024 21:50:41 GMT
Server:    Werkzeug/2.3.8 Python/3.12.0
Set-Cookie:
access_token_cookie=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6dHJ1ZSwiaWF0IjoxNzA0MjMyMjQxLCJqdGkiOiJjMTI3MWJiZS0xMDExLTRhODgtOTkxMS01Y2NjMTVjYWVlNTgiLCJ0eXBlIjoiYWNjZXNzIiwic3ViIjoiNzhhM2ZhYmQtODcxMC00ZjlmLWI5MzMtNzcwYWE3NWJhMDQ2IiwibmJmIjoxNzA0MjMyMjQxLCJjc3JmIjoiYjlkZGY3NzEtN2UyMy00ZTdiLTlkOGMtNjJiMjc0N2ZmZjk4IiwiZXhwIjoxNzA0MjM1ODQxfQ.OHAY9puF1pt7rO0IhwTr63Xx-OUoAU-_b9-YtecdQ10; Domain=127.0.0.1; HttpOnly; Path=/
Set-Cookie:
csrf_access_token=b9ddf771-7e23-4e7b-9d8c-62b2747fff98; Domain=127.0.0.1; Path=/
Set-Cookie:
csrf_refresh_token=eded9b74-ed42-4b07-9b9b-f51ee4af923a; Domain=127.0.0.1; Path=/
Set-Cookie:
refresh_token_cookie=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTcwNDIzMjI0MSwianRpIjoiYzEwMTdlMmUtZWVhMy00OTE0LWJiNTUtZWE5NTI3ZWNlYmUzIiwidHlwZSI6InJlZnJlc2giLCJzdWIiOiI3OGEzZmFiZC04NzEwLTRmOWYtYjkzMy03NzBhYTc1YmEwNDYiLCJuYmYiOjE3MDQyMzIyNDEsImNzcmYiOiJlZGVkOWI3NC1lZDQyLTRiMDctOWI5Yi1mNTFlZTRhZjkyM2EiLCJleHAiOjE3MDY4MjQyNDF9.XjfphInWFFULAKbg7aTR_-AqaFgVuiL2yCvZFnaUq1Q; Domain=127.0.0.1; HttpOnly; Path=/

Leave a Comment