I have an Angular app that needs to call a Flask server that use sessions to store information between requests.
I also have an older JS app that called the same server using XMLHttpRequest, that I am replacing with the new Angular app.
The trouble is that when the old app was making a request, session cookies were working as expected but now with the angular app it does not.
All interactions are done over localhost. The Flask server is accessible from localhost:5000, and the Angular app from localhost:4200.
The old app was doing request like this:
var xhttp = new XMLHttpRequest();
xhttp.open("GET", "http://localhost:5000/api/getAll", true);
xhttp.withCredentials = true;
xhttp.send();
The Angular app is doing like so:
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders, } from '#angular/common/http';
import { Observable } from 'rxjs';
const httpOptions = {
withCredentials: true,
headers: new HttpHeaders({
'Content-Type': 'application/json',
'charset': 'UTF-8',
})
};
#Injectable()
export class ServerService {
url = "http://localhost:5000/api/"
constructor(private http:HttpClient) { }
getAll(): Observable<string>{
return this.http.get<string>(this.url + 'getAll', httpOptions);
}
login (username: string): Observable<string> {
return this.http.post<string>(this.url + 'login', JSON.stringify({"username": username}), httpOptions)
}
}
And the Flask server:
from flask import Flask, session, request, jsonify
from flask_cors import CORS
import os
import Person
import multiprocessing as mp
import json
import Insurance
import datetime
import Functions
import missingVal
app = Flask(__name__)
CORS(app, supports_credentials=True)
# set the secret key. keep this really secret:
# The value come from calling os.urandom(24)
# See https://stackoverflow.com/a/18709356/3729797 for more information
# app.secret_key = b'fL\xabV\x85\x11\x90\x81\x84\xe0\xa7\xf1\xc7\xd5\xf6\xec\x8f\xd1\xc0\xa4\xee)z\xf0'
app.config['SECRET_KEY'] = b'fL\xabV\x85\x11\x90\x81\x84\xe0\xa7\xf1\xc7\xd5\xf6\xec\x8f\xd1\xc0\xa4\xee)z\xf0'
#app.route('/api/getAll')
def getAll():
response = jsonify()
if 'username' in session:
user = users[session['username']]
# some more logic here
response = jsonify({'username': session['username']})
return response
# login and account creation
#app.route('/api/login', methods=['POST'])
def login():
response = jsonify()
if users.get(request.json.get('username')) is not None:
session['username'] = request.json.get('username')
# some more logic here
response = jsonify({'username': session['username']})
response.headers.add('Access-Control-Allow-Methods',
'GET, POST, OPTIONS, PUT, PATCH, DELETE')
response.headers.add('Access-Control-Allow-Headers',
"Origin, X-Requested-With, Content-Type, Accept, x-auth")
return response
if __name__ == '__main__':
# some more logic here
app.run(host='localhost', threaded=True
The problem is that when I log in, it push information into the session, and when I do another request, I check if that information is in the session but it does not.
I found a lot of other related question on StackOverflow:
this one has to do with setting the secret_key multiple times, which is not my problem.
this one talk about static versus dynamic configuration in init but I don't think it has anything to do with my problem? Tell me if I'm wrong.
this one and this other one had trouble because their payload inside the cookie was too big, only 4096bytes or less are allowed it seems. But I only put a username that is a few letters into my cookie so I don't believe it is my problem.
this one I thought was related to my problem because it deals with localhost, but it turns out that it was because OP was mixing requests on 127.0.0.1 and localhost and cookies are processed separately by flask apparently. I do all my request on localhost so not related I believe.
I'm a bit lost right now, there is probably something very obvious I am missing but can't figure it out, any suggestion appreciated
I got it working by adding
response.headers.add('Access-Control-Allow-Headers',
"Origin, X-Requested-With, Content-Type, Accept, x-auth")
in the Flask server before sending back all requests.
For example
#app.route('/api/doSomething', methods=['POST'])
def doSomething():
response = jsonify()
if 'username' in session:
# some logic here
response = jsonify(someData)
# here is the line I added
response.headers.add('Access-Control-Allow-Headers',
"Origin, X-Requested-With, Content-Type, Accept, x-auth")
return response
Apparently it is needed when doing CORS, some good informations on MDN
Related
For development I am using vuejs which is being served by webpack at my local address: 172.18.0.77:8080 and flask that is run by Werkzeug at address 172.18.0.77:5000. I am trying to set cookie to some GET request by running this code:
response_data = Response(json.dumps(some_json_data, indent=True), status=200, mimetype='application/json')
response_data.set_cookie('user_session_id', value='12345', domain='172.18.0.77:8080')
return response_data
But when I am trying to read this cookie with following code request.cookies.get('user_session_id') I am receiving only None value.
I also tried to set cookie by changing domain to 172.18.0.77 like:
response_data.set_cookie('user_session_id', value='12345', domain='172.18.0.77')
But it also doesn't work
if you use axios in vuejs, i suggest that you can add withCredentials: true
const instance = axios.create({
withCredentials: true,
....
})
And in flask
#app.after_request
def handle_credentials(response):
response.headers["Access-Control-Allow-Credentials"] = True
return response
I'm studing about RESFful API with python.
I want to build a my restful api server,
but i have a problem,
i don't know how my api server returns proper data by reqeust each parameters
(request code sample)
the request code wants to get the information about 'item' : 'sword'
import requests
import json
url = "https://theURL"
querystring={"item":"sword"}
response = requests.request("GET", url, params=querystring)
print (response.json())
(API server response code sample, by flask python)
from flask import Flask, url_for
from flask_restful import Resource, Api, abort, reqparse
app = Flask(__name__)
api = Api(app)
TODOS = {
"version":"2.0",
"resultCode":"OK",
"output":{
{
"item" :"sword"
"price": 300,
"damage": 20,
},
{
"item" :"gun"
"price": 500,
"damage": 30,
},
},
}
class Todo(Resource):
def post(self):
return TODOS
api.add_resource(Todo, '/item.price')
if __name__ == "__main__":
app.run(debug=True, host ="192.168.0.8", port = 8080)
So i want to know how i use a code in response api server for returning 'item price' data by reqeusted parameters 'item : sword'
I just want to get the selected parameter's item price and damage information.
I thought it might be very easy, i'm tried to search the example but i couldn't find proper sample code.
I'm not a Flask-expert, but this helps setting up and running a minimalistic Flask-server. Then, this explains how to return json-formatted data from your server, and finally how to request and interpret the json-response can be found here. Summarized below:
Server returning a json-formatted data:
from flask import Flask
from flask import jsonify
app = Flask(__name__)
#app.route('/')
#app.route('/index')
def hello():
return "Hello, World!"
#app.route('/request_sword')
def grant_sword():
return jsonify({"sword": {"name":"Excalibur","price":"stack overflow"}})
Client requesting json-formatted data:
import json
import urllib.request
url = "http://127.0.0.1:5000/request_sword" ## My local host flask server
data = urllib.request.urlopen(url)
response = json.loads(data.read())
print(response)
That's all really. You may also just enter the url in your browser, which will correctly read the json-data:
I am trying to connect a client with a server via ajax requests.
My client, running on localhost:8080, has a button which invokes a function that does a simple ajax request to a locally running server, localhost:5000.
onClick function:
handleClick() {
console.log("check flights button was clicked!");
console.log(this.state);
const baseUrl = 'localhost:5000'
ajax.get(`${baseUrl}/flights/${this.state.origin}_${this.state.destination}`)
.end((error, response) => {
if (!error && response) {
console.log('got a valid response from the server')
} else {
console.log(`Error fetching data from the server: `, error)
}
});}
the (very) simple server, implemented with Flask, is as follows:
from flask import Flask, request
from flask_restful import Resource, Api
from json import dumps
app = Flask(__name__)
api = Api(app)
class Flights(Resource):
def get(self, origin, destination):
answer = 'we got some data ' + origin + ' ' + destination
print answer
api.add_resource(Flights, '/flights/<string:origin>_<string:destination>')
if __name__ == '__main__':
app.run()
I can access the server if i simply go to localhost:5000/flights/origin_destination, which prints a message saying it received the origin and destination.
However, when I try to do the ajax request, I get the following error:
XMLHttpRequest cannot load localhost:5000/flights/Porto_Lisboa. Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, https.
Any suggestions on how to correct this behaviour?
Thank you in advance.
As the error suggest, you're missing the protocol name in your request. Try to add it at the start of the string :
const baseUrl = 'http://localhost:5000'
instead of just localhost:5000
I have a simple Flask backend for the API, and want to make a POST.
I'm not sure the error is-I get the error message, but am unsure if its an Angular or Flask issue. I tried request.get_json() in my Flask, and got
{BadRequest}400 BadRequest: The browser (or proxy) sent a request that
this server could not understand
Angular2 call in my service.ts:
import { Injectable } from '#angular/core';
import { Http, Response } from '#angular/http';
import { Observable} from 'rxjs/Rx';
import { Headers } from '#angular/http';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
#Injectable()
export class NicksService {
private headers = new Headers({'Content-Type': 'application/json'});
nickUrl: string = "http://localhost:5000/nick";
constructor(private http: Http) { }
// Send the nick to the server to see if it is valid
checkNickValidity(nickName: string): Observable<string>{
return this.http.post(this.nickUrl, JSON.stringify({nick: nickName}), {headers: this.headers})
.map(this.extractData)
.catch(this.handleError)
}
Flask backend:
from flask_cors import CORS
from flask.ext.api import FlaskAPI
from flask import request
app = FlaskAPI(__name__)
CORS(app)
#app.route('/')
#app.route('/nick', methods=['POST'])
def check_nick():
try:
nick = request.json
if nick =='AmandaPanda':
return 'Good'
else:
return 'No nick found'
except:
return 'error'
if __name__ == '__main__':
app.run()
I think you have problems on both sides of your application.
Client side:
Angular's Http's post method receives any object in for the body parameter, meaning you don't need to stringify it.
E.g.:
this.http.post(this.nickUrl, {nick: nickName}, {headers: this.headers})
Server side:
Once the client's request arrived properly to the server, you are not getting the data properly.
What you need to do is something like this:
from flask import jsonify
(... your code ...)
data = request.json
nick = data.get('nick')
if nick == 'AmandaPanda':
return jsonify({'message': 'good'}), 200
else:
return jsonify({'message': 'no nick found'}), 400
Here, the json data is stored as a dictionary in the variable data. Later, the nick is obtained from the nick key of said dictionary.
Finally, it is a better practice to return to the client in json form. That's what Flask's jsonify method is for. Notice the 200 and 400 status codes appended to the response, to indicate to the client if the request was properly accepted (200) or if it was a bad request (400).
The Setup:
The system should be as follows:
There are two AWS loadbalancers, each of which runs a Docker container. One is a private loadbalancer, which contains an arbitrary service that ought to be protected from unauthenticated access; the other is a public-facing loadbalancer, which contains the authentication portal.
Inside the Docker container (on the public loadbalancer) is an NginX server listening on port 80, and a Flask app serving on port 8000.
NginX uses the auth_request directive to make a subrequest to the Flask app (passing along credentials as headers on the request), expecting either 200 or 401 in response.
The Flask app pulls the credentials from the request and checks if that username is already part of flask.session. If so, it immediately returns a 200 response; otherwise, it attempts to authenticate against an external source of truth. If it's successful, that user is added to flask.session and the app returns a 200 response; otherwise, it will return a 401 response.
There also exists a check for expiration; if a user's been logged in for more than an hour, for example, the app should remove them from flask.session and return a 401 response. The user can then make a new login request.
If the response is 200, traffic is routed to the private loadbalancer.
The user's browser should cache the credentials that they've submitted, and so each new request should be able to immediately see that the user is part of flask.session and avoid making a new authentication attempt.
The Issue:
Seemingly at random, while refreshing or navigating the protected resource (after successful authentication), sometimes the authentication popup appears and the user will be required to authenticate again. They can submit, and a single resource will be loaded before being prompted again for reauthentication.
Example:
The protected resource is a static website consisting of an index page, a CSS file, and three images. After initial authentication, the user refreshes the page several times. One of these times, the authentication prompt will be triggered. They will enter their credentials again, and the index page will load. They'll be prompted again for the CSS file, and then again for each image.
The Code
I'm not sure how many things I'll need to link here in order to root out the issue, so I'll begin with the nginx file responsible for making the auth_request subrequest and the subsequent routing, and the two python files responsible for making the auth requests and handling sessions.
nginx.default
server {
listen 80;
server_name _;
location / {
auth_request /auth;
proxy_pass {{resource_endpoint}};
}
location /auth {
proxy_pass {{auth_backend}};
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
}
location /logout {
proxy_pass {{auth_backend}};
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
}
}
app.py
import flask
import auth0
import os
app = flask.Flask(__name__)
app.secret_key = os.getenv("SECRET_KEY", 'sooper seekrit')
#app.route('/auth', methods=['GET'])
#auth0.requires_auth
def login():
print("logging in")
resp_text = (
"Authentication successful."
)
return flask.Response(
response=resp_text,
status=200,
)
#app.route('/logout', methods=['GET'])
def logout():
# To successfully invalidate a user, we must both clear the Flask session
# as well as direct them to a '401: Unauthorized' route.
# http://stackoverflow.com/questions/233507/how-to-log-out-user-from-web-site-using-basic-authentication
print("logging out")
flask.session.clear()
return flask.Response(
response='Logout',
status=401,
)
if __name__ == "__main__":
app.debug = True
from gevent.wsgi import WSGIServer
http_server = WSGIServer(('', 8000), app)
http_server.serve_forever()
auth0.py
import json
import requests
import flask
import datetime
import os
from functools import wraps
def check_auth(username, password):
if 'username' in flask.session:
import pdb; pdb.set_trace()
if 'logged_in' in flask.session:
now = datetime.datetime.now()
expiry_window = datetime.timedelta(
minutes=int(os.getenv('AUTH0_EXPIRY'))
)
if flask.session['logged_in'] >= now - expiry_window:
return True
else:
flask.session.pop('username', None)
data = {
'client_id': os.getenv("AUTH0_CLIENT_ID"),
'username': username,
'password': password,
'id_token': '',
'connection': os.getenv("AUTH0_CONNECTION"),
'grant_type': 'password',
'scope': 'openid',
'device': ''
}
resp = requests.post(
url="https://" + os.getenv('AUTH0_DOMAIN') + "/oauth/ro",
data=json.dumps(data),
headers={"Content-type": "application/json"}
)
if 'error' in json.loads(resp.text):
return False
else:
flask.session['username'] = username
flask.session['logged_in'] = datetime.datetime.now()
return True
def authenticate():
return flask.Response(
'Could not verify your access level for that URL.\n'
'You have to login with proper credentials', 401,
{'WWW-Authenticate': 'Basic realm="Login Required"'},
)
def requires_auth(f):
#wraps(f)
def decorated(*args, **kwargs):
auth = flask.request.authorization
if not auth or not check_auth(auth.username, auth.password):
return authenticate()
return f(*args, **kwargs)
return decorated