Home > Software design >  Difference between fetch and postman's results
Difference between fetch and postman's results

Time:04-20

I have a simple login backend which has two routes login and get_user. After a user is logged in, a cookie is set such that it enables other routes like get_user. I tested this backend with Postman and after correct login, the cookie is set and get_user responds with user's data.

However, when I try to use fetch or axios in React and JS, I get problems. After I fetch login, I can see the cookie is sent, however, fetch get_user acts like cookie is not set at all.

I provided a minimal example to show that server side session somehow doesn't work with fetch:

Frontend:

<!DOCTYPE html>
<html>
<body>

<h1> Set value: </h1> <h2 id="set_value"></h2>
<h1> Get value: </h1> <h2 id="get_value"></h2>

</body>
<script>
    // ..\..\Flask_general_framework\backend\venv\Scripts\Activate.ps1
    async function Set_user_fetch()
    {
        // Get user        
        let user = await fetch('http://127.0.0.1:5000/set/gggg', {
            'method': 'GET',
            //credentials: 'include',
            mode: 'cors',
            credentials: "same-origin",
            headers: {'Content-type': 'application/json', 'Accept': 'application/json',
            'Access-Control-Allow-Origin': '*', // Required for CORS support to work
            'Access-Control-Allow-Credentials': true, // Required for cookies, authorization headers with HTTPS},
            'Access-Control-Allow-Headers': 'Content-Type, X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Date, X-Api-Version, X-File-Name',
            }
        })
        user = await user.json();
        console.log("await user:", user);
        document.getElementById("set_value").innerHTML = user.value;
    }

    async function Get_user_fetch()
    {                
        let user = await fetch('http://127.0.0.1:5000/get', {
            'method': 'GET',
            //credentials: 'include',
            credentials: "same-origin",
            mode: 'cors',            
            headers: {'Content-type': 'application/json', 'Accept': 'application/json',
            'Access-Control-Allow-Origin': '*', // Required for CORS support to work
            'Access-Control-Allow-Credentials': true, // Required for cookies, authorization headers with HTTPS},
            'Access-Control-Allow-Headers': 'Content-Type, X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Date, X-Api-Version, X-File-Name',
         }
        })
        user = await user.json();        
        console.log("await user:", user);
        document.getElementById("get_value").innerHTML = user.value;
    }    

    Set_user_fetch().then( () => {
        Get_user_fetch();
    });
    
</script>
</html>

Backend:

from re import I
from flask import Flask, session
from flask_session import Session
from flask_cors import CORS
import redis

import datetime as dt
app = Flask(__name__)
CORS(app, supports_credentials=True)
app.config['SECRET_KEY'] = 'super secret key'
#app.config['SESSION_TYPE'] = 'redis'
app.config['SESSION_TYPE'] = 'filesystem'
app.config['SESSION_PERMANENT'] = True
#app.config['SESSION_REDIS'] =  redis.from_url('redis://localhost:9876')
app.config['PERMANENT_SESSION_LIFETIME'] = dt.timedelta(days=7).total_seconds()
server_session = Session()     
server_session.init_app(app)

@app.route('/set/<value>', methods=['GET', 'POST'])
def set_value(value):
    session['value'] = value
    return {"value": value}

@app.route('/get', methods=['GET', 'POST'])
def get_value():
    return {"value": session.get('value', 'None')}

app.run(host='127.0.0.1', port=5000, debug=True)

CodePudding user response:

Server Side

In order to support cross-site cookies in modern browsers, you need to configure your server to use the Set-Cookie attribute SameSite=None (see Flask-specific example here). Unfortunately, this also requires the Secure attribute and an HTTPS enabled server.

For local development, you can get around this by serving your client and server on the same hostname, eg localhost with SameSite=Lax (or omitting SameSite which defaults to "Lax").

By "same hostname" I mean that if your frontend code makes requests to localhost:5000, you should open it in your browser at http://localhost:<frontend-port>. Similarly, if you make requests to 127.0.0.1:5000, you should open it at http://127.0.0.1:<frontend-port>.

Lax same-site restrictions don't come into play if only the ports differ.

Client Side

You have a few problems here...

  1. You're sending headers in your request that do not belong there. Access-Control-Allow-* are response headers that must come from the server.
  2. You set credentials to same-origin but are sending a request to a different host. To use cookies, you need to set credentials to "include". See Request.credentials.
  3. You have no error handling for non-successful requests.

You're also setting a lot of redundant properties and headers and can trim down your code significantly.

async function Set_user_fetch() {
  const res = await fetch("http://127.0.0.1:5000/set/gggg", {
    credentials: "include",
  });

  if (!res.ok) {
    throw new Error(`${res.status}: ${await res.text()}`);
  }

  const user = await res.json();
  console.log("await user:", user);
  document.getElementById("set_value").innerHTML = user.value;
}

async function Get_user_fetch() {                
  const res = await fetch("http://127.0.0.1:5000/get", {
    credentials: "include",
  });

  if (!res.ok) {
    throw new Error(`${res.status}: ${await res.text()}`);
  }

  const user = await res.json();        
  console.log("await user:", user);
  document.getElementById("get_value").innerHTML = user.value;
}    

If you were using Axios, you would set the withCredentials config to true

axios.get("http://127.0.0.1:5000/set/gggg", {
  withCredentials: true
});
  • Related