Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flask Session Not Persisting (Postman works, Javascript doesn't)

I'm developing a Flask server to communicate between some backend Python functionality and Javascript clients over the web. I'm attempting to utilize Flask's session variable to store user specific data over the course of their time interacting with the app. I've removed most of the application specific code below but the core problem I'm experiencing remains.

Here is my the code for my (simplified) Flask app:

import json
import os
from flask import Flask, jsonify, request, session

app = Flask(__name__)
app.secret_key = 'my_secret_key'

@app.route('/', methods=['GET'])
def run():
  session['hello'] = 'world'
  return jsonify(session['hello'])

@app.route('/update', methods=['POST'])
def update():
  return jsonify(session['hello'])

if __name__ == '__main__':
  app.run(host='0.0.0.0')

Utilizing Postman, I can make a GET request to my server and receive the expected output of "world". I can then make a POST request with an arbitrary body and receive the same expected output of "world" (again using Postman).

When using Chrome, I can visit my server IP and see the expected output "world" on the page. I can also manually make a GET request using Javascript (in Chrome's console) and receive the same response as expected. However, my problem arises when trying to send a POST request to the server using Javascript; the server shows a KeyError: 'hello' when trying to make this request.

Here is the Javascript I'm using to make the POST request:

var url = 'http://my_server_ip/update';
fetch(url, {
  method: 'POST',
  body: JSON.stringify('arbitrary_string'),
  headers: new Headers({
    'Content-Type': 'application/json'
  })
})
.then(response => response.json())
.then((data) => {
  console.log(data);
})

What's going wrong here? Why can I make the GET/POST requests with Postman just fine but run into errors making the same requests with Javascript?

like image 402
sam Avatar asked Apr 07 '18 22:04

sam


People also ask

How session is maintained in Flask?

Flask-Session is an extension for Flask that supports Server-side Session to your application. The Session is the time between the client logs in to the server and logs out of the server. The data that is required to be saved in the Session is stored in a temporary directory on the server.

How long does Flask session last?

Creating a permanent session allows us to define how long that session lasts. The default duration of a permanent session is 30 days.

Does flask login use session?

By default, Flask-Login uses sessions for authentication. This means you must set the secret key on your application, otherwise Flask will give you an error message telling you to do so. See the Flask documentation on sessions to see how to set a secret key.

What is permanent session Flask?

By default, Flask uses volatile sessions, which means the session cookie is set to expire when browser closes. In order to use permanent sessions, which will use a cookie with a defined expiration date, one should set session.


2 Answers

The caveats section of the fetch documentation says:

By default, fetch won't send or receive any cookies from the server, resulting in unauthenticated requests if the site relies on maintaining a user session.

It is recommended to use AJAX to exchange information with Flask views.

Meanwhile, in your code for the Flask app, the session object is a dictionary. Now, if you access a dictionary with its key session['hello'] and if this key does not exist, a Keyerror is raised. To get around this error, you can use the get() method for dictionaries.

What is happening is: the fetch request does not find the hello key(or GET the session value from the Flask view) in the Flask session.

user = session.get('hello')
return jsonify(session_info=user)

But this will still give you a null value for the session { session_info: null }. Why is that so?

When you send GET/POST requests to the Flask server, the session is initialized and queried from within Flask. However, when you send a Javascript fetch POST request, you must first GET the session value from Flask and then send it as a POST request to your Flask view which returns the session information.

In your code, when the POST request is triggered from fetch, when I send the payload data to Flask, it is received correctly and you check this using request.get_json() in the Flask view:

@app.route('/update', methods=['POST'])
def update():
  user = session.get('hello')
  payload = request.get_json()
  return jsonify(session_info=user, payload=payload)

This will return { payload: 'arbitrary_string', session_info: null }. This also shows that fetch does not receive the session information because we did not call GET first to get the session information from Flask.

Remember: The Flask session lives on the Flask server. To send/receive information through Javascript you must make individual calls unless there is a provision to store session cookies.

const fetch = require('node-fetch');

var url_get = 'http://my_server_ip';
var url_post = 'http://my_server_ip/update';
fetch(url_get, {
  method:'GET'
}).then((response)=>response.json()).then((data) =>fetch(url_post, {
  method: 'POST',
  body: JSON.stringify(data),
  dataType:'json',
  headers: {
    'Content-Type': 'application/json'
  }
})
.then(response => response.json())
.then((postdata) => {
  console.log(postdata);
}));

The Flask views will change slightly:

@app.route('/', methods=['GET'])
def set_session():
    session['hello'] = 'world'
    return jsonify(session['hello'])

@app.route('/update', methods=['POST'])
def update():
    payload = request.get_json()
    return jsonify(session_info=payload)

When you trigger the Javacript request now, the output will be: { session_info: 'world' }

like image 68
amanb Avatar answered Oct 22 '22 12:10

amanb


After a few hours of testing, I managed to figure out the issue. Although I think @amanb's answer highlights the problem, I'm going to answer my own question because what I found is ultimately a simpler solution.

In order to make the POST request return the expected value, I simply needed to add a credentials: 'same-origin' line to the fetch body. This looks like the following:

var url = 'http://my_server_ip/update';
fetch(url, {
  method: 'POST',
  body: JSON.stringify('arbitrary_string'),
  credentials: 'same-origin',   // this line has been added
  headers: {
    'Content-Type': 'application/json'
  }
})
.then(response => response.json())
.then((data) => {
  console.log(data);
})

According to Mozilla's Fetch usage guide,

By default, fetch won't send or receive any cookies from the server, resulting in unauthenticated requests if the site relies on maintaining a user session.

So it seems I looked over this. Changing the credentials to allow communication of the cookie/session between client and server resolved the issue.

like image 6
sam Avatar answered Oct 22 '22 12:10

sam