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?
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.
Creating a permanent session allows us to define how long that session lasts. The default duration of a permanent session is 30 days.
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.
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.
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 return
s 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' }
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With