I have a javascript fetch call to an api (on a different domain) and I'm passing along credentials. On the other Python Flask API, I have CORS and 'Access-Control-Allow-Credentials' set to true on all requests.
Python Webservice
app = Flask(__name__)
app.secret_key = os.getenv('SESSION_SECRET')
app.config.from_object(__name__)
CORS(app)
#app.after_request
def add_header(response):
response.headers['Access-Control-Allow-Credentials'] = 'true'
return response
#app.route('/login', methods=['POST'])
#captcha_check
def login():
data = request.get_json(force=True) # Force body to be read as JSON
email = data.get('email')
password = data.get('password')
# Authenticate users account and password
if auth.verify_password(email, password):
# Password is correct, return temporary session ID
username_query = db.get_username_by_email(email)
if username_query['found']:
session['email'] = email
return jsonify({'status': 200, 'email': email}), 200
else:
return jsonify({'status': 404, 'error': 'user-email-not-found'}), 404
else:
# Incorrect password specified, return Unauthorized Code
return jsonify({'status': 401, 'error': 'incorrect-password'}), 401
#app.route('/logout', methods=['POST'])
def logout():
print(session, file=sys.stdout)
session.clear()
print(session.pop('email', None), file=sys.stdout)
return jsonify({'status': 200}), 200
Javascript fetch call
async function signInPost(data) {
const response = await fetch(serverAddress + '/login', {
method: 'POST',
credentials: 'include',
headers: {
'Access-Control-Allow-Origin': 'http://localhost:5000',
'Access-Control-Allow-Credentials': 'true'
},
body: JSON.stringify(data)
});
// Wait for response from server, then parse the body of the response in json format
return await response.json();
}
async function signOutPost() {
const response = await fetch(serverAddress + '/logout', {
method: 'POST',
headers: {
'Access-Control-Allow-Origin': 'http://localhost:5000',
'Access-Control-Allow-Credentials': 'true'
},
credentials: 'include'
});
return await response.json();
}
I know the cookie is being set with login becuase I have other endpoints that check the status of the cookie to see if a person is signed in or not. What I'm confused about is why this doesn't always invalidate the cookie. On chrome the network syas the status of the first api call is "(cancled)", but then sometimes if I click it one or two more times it will eventually sign out. On Firefox and Safari it will esentially never sign me out.
Flask is getting the api calls and returning a 200 status, but the browsers are not respecting what is being returned.
My current assumption is that it has something to do with the OPTIONS method that get's called in advance blocking the request, but I'm not sure how to get around this.
Why dont you pass in your config.py file something like this
CORS_SUPPORTS_CREDENTIALS = True
instead of passing in the requests?
Related
I'm working on an app with flask backend and react front,
I want to save the username in a session and display it in the front post-login.
here is the current process:
configurations at the head of app.py (flask-server main file)
app = Flask(__name__)
app.config["JWT_SECRET_KEY"] = "key"
app.config["SECRET_KEY"] = "pass"
app.config["JWT_ACCESS_TOKEN_EXPIRES"] = timedelta(hours=1)
app.config["SESSION_TYPE"] = 'filesystem'
app.config["SESSION_PERMANENT"] = False
# app.config.from_object(__name__)
CORS(app)
Session(app)
jwt = JWTManager(app)
Login button with handler:
const data = {
username: loginForm.username,
password: loginForm.password
}
fetch("http://localhost:5000/token", {
method: "POST",
body: JSON.stringify(data)
})
.then((response) => {...
the route for "/token" - if the credentials are ok it renders my user homepage, and it works fine.
#app.route('/token', methods=["POST"])
def create_token():
username = json.loads(request.data)["username"]
password = json.loads(request.data)["password"]
sql_string = f"""select username, password from admins where username='{username}'"""
result = sql_call(sql_string) //this is a helper functino in db_connector file
if(result):
session["username"] = username
access_token = create_access_token(identity=username) //another helper function
response = app.response_class(response=json.dumps({"access_token": access_token}),
status=200,
mimetype='application/json')
response.headers.add('Access-Control-Allow-Origin', '*')
return response
on the app main page i have a span of "Hello " that tries to fetch the username from the session, sending request to route "/username":
#app.route("/username")
def getUsername():
username = session["username"]
response = jsonify({"username": "admin"})
return response
But it finds no key "username" in the session, and also the SID in 4 and SID in 3 are different.
how can I retrieve this data back?
my flask runs on port 5000 and react on port 3000,
thanks
In step 4 use this to get username
session.get("username")
Ok, I didn't manage to fix it,
session.get("username" also didn't work)
I noticed that the server is working fine when sending requests from postman, so the problem might be in React.
I used the get_jtw()
over the session and it works well :)
I am trying to login from my website developed in Angular to the back end flask API, The backend uses bcrypt for encoding and pymongo for interacting with a MongoDB. In postman, the login endpoint works fine but when an attempt to log in on the client side is made I receive a 401: UNAUTHORIZED ERROR. Can someone advise what the issue is/where I am going wrong?
The login function on the client side:
onLogin() {
let postData = new FormData();
postData.append('username', this.loginForm.get('username').value);
postData.append('password', this.loginForm.get('password').value);
this.http.post('http://localhost:5000/api/v1.0/login', postData)
.subscribe((_response: any) => {
console.log(this.loginForm.value);
this.loginForm.reset();
})
}
The login endpoint in backend:
app = Flask(__name__)
CORS(app)
#app.route('/api/v1.0/login', methods=['POST'])
def login():
auth = request.authorization
if auth:
user = users.find_one({'username': auth.username})
if user is not None:
if bcrypt.checkpw(bytes(auth.password, 'utf-8'),
user["password"]):
token = jwt.encode( \
{'user' : auth.username,
'admin': user["admin"],
'exp' : datetime.datetime.utcnow() + \
datetime.timedelta(minutes=30)
}, app.config['SECRET_KEY'])
return make_response(jsonify( \
{'token':token.decode('UTF-8')}), 200)
else:
return make_response(jsonify( \
{'message':'Bad password'}), 401)
else:
return make_response(jsonify( \
{'message':'Bad username'}), 401)
In order to use request.authorization you need to send the credentials in the Authorization-header. Try this on the client:
onLogin() {
const usn = this.loginForm.get('username').value;
const pwd = this.loginForm.get('password').value;
this.http.post('http://localhost:5000/api/v1.0/login', null, {
headers: new HttpHeaders({
Authorization: `Basic ${btoa(`${usn}:${pwd}`)}`
})
}).subscribe((_response: any) => {
console.log(_response);
});
}
As a sidenote; the credentials can be easily decoded by using atob(), so make sure you are using a secure connection (HTTPS) when not on localhost.
My Cloud Function is very similar to the example in the docs. It looks like this:
def states(request):
"""Responds to any HTTP request.
Args:
request (flask.Request): HTTP request object.
Returns:
All states in the database.
"""
if request.method == 'OPTIONS':
headers = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'POST',
'Access-Control-Allow-Headers': '*'
}
return ('', 204, headers)
# request_json = request.get_json()
headers = {
'Access-Control-Allow-Origin': '*'
}
stmt = 'SELECT * FROM states'
try:
with db.connect() as conn:
response = conn.execute(stmt)
states_dict = {}
for row in response:
states_dict[row['name']] = str(row)
print(states_dict)
return ({'data': states_dict}, 200, headers)
except Exception as e:
error = 'Error: {}'.format(str(e))
print(error)
return (error, 200, headers)
When I test this function from the Cloud Functions UI, it returns results from Cloud SQL. However, when I call this from Vue using the following code, it gives me the following error in the console:
Access to fetch at 'https://<myappinfohere>.cloudfunctions.net/states' from origin 'https://myappinfohere.web.app' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
Can someone help me debug why this is happening? I thought I handled CORS requests properly, and when I proxy the database locally I can even emulate the function and get it working locally. I cannot get it working in production, however.
EDIT: Here is the code on the client I'm using to access this function.
db.js
import firebase from 'firebase'
const config = {
apiKey: REDACTED
authDomain: REDACTED,
databaseURL: REDACTED,
projectId: REDACTED,
storageBucket: REDACTED,
messagingSenderId: REDACTED,
appId: REDACTED,
measurementId: REDACTED
}
const app = firebase.initializeApp(config)
// app.functions().useFunctionsEmulator('http://localhost:8081')
const db = firebase.firestore()
var gcFunc = firebase.functions().httpsCallable('states')
export {
db,
gcFunc,
app
}
My Vuex code:
const fb = require('#/db.js')
...
fb.gcFunc({}).then((result) => {
context.commit('setAllStates', Object.values(result.data))
})
I have a backend is implemented in flask and flask_restful and has a number of different routes. My frontend is running on another origin which means that I used flask_curse in order to allow my frontend to sent requests to my backend.
Bellow you can see the initialization of my application:
app = Flask(__name__)
app.register_blueprint(check_routes_page)
CORS(app, supports_credentials=True)
Here is the route I am calling from the front-end:
#check_routes_page.route(API_URL +API_VERSION +'check/email', methods=['POST'])
def check_email():
email = request.form['email']
user = User.query.filter(User.email == email).first()
if user:
return jsonify({'success':True}), 200
else:
return jsonify({'success': False}), 404
When I use Postman to sent the request everything works perfectly. When I do however sent a request from my application I always get back a 400. I have also change the content type without any success.
Here is the request I sent from my application.
checkMailAddress(email: string): boolean {
let requestUrl = 'http://X/application/api/V0.1/check/email';
let headers = new Headers({ 'Content-Type': 'application/x-www-form-urlencoded' });
let body = JSON.parse('{"email":"' + email + '"}');
let options = new RequestOptions({ headers: headers });
let respo: any
let us = this.http.post(requestUrl, body, options)
.map((response) => response.json())
.subscribe(
function(response) {
console.log("Success Response:" + response);
respo = response;
},
function(error) {
console.log("Error happened" + error);
},
function() {
console.log("the subscription is completed");
console.log(respo.status);
}
);
return true;
}
When I sent a request with content type Json the client sends an option request first (which returns code 200) but than still fails with the actual request.
I am thankful for any hints or advice.
Try to change this line:
user = User.query.filter(User.email == email).first()
to:
user = User.query.filter(email=email).first()
When this line fails email = request.form['email'] then flask sends 400 bad request.
Check if the request body is json or any other type using request.isjson() if not then convert data to json using data = request.getjson()
I'm using Locust (which uses Requests) to do page load tests. The page has a popup requesting username and password to access the page and there is a standard login page.
I'm using the client.auth to authenticate the first time (on the popup) to open the page and send data on the POST request to login the account.
The problem is that Locust never displays a failure, even when I skip the second authentication. In other words, if I do the client.auth authentication I can GET any page (even the ones for which authentication is needed, and I skip the second authentication) and Locust does not display a 401 error. So I'm doing something wrong here.
Here is the code that I'm using:
class UserBehavior(TaskSet):
def on_start(self):
self.login()
def login(self):
# Set basic auth credentials
if BASIC_AUTH_CREDENTIALS:
self.client.auth = BASIC_AUTH_CREDENTIALS
headers = {
'referer': 'http://myreferer.com'
}
data = {
'username': 'John',
'password': 'Doe',
'csrfmiddlewaretoken': '123456712345671234567'
}
r = self.client.post('/login', data=data, headers=headers)
#task
def progress(self):
self.client.get("/needed/authorization")
What I want is to measure the time it takes a page to load when a number of users try to load it. I have a couple of pages that I measure this on, one that loads fast the other much slower, but the test displays the same values for both. I think that Locust's simulated users get redirected to the login page every time and therefor they have the same response time and don't report a 401 or 404, or any error for that matter.
Please help me do this properly.
For testing the login required pages you should send loged-in user's info (cookie) in the header.
class UserBehavior(TaskSet):
cookie = ""
def on_start(self):
self.cookie = self.get_user_cookie()
def get_user_cookie(self):
# Set basic auth credentials
if BASIC_AUTH_CREDENTIALS:
self.client.auth = BASIC_AUTH_CREDENTIALS
headers = {
'referer': 'http://myreferer.com'
}
data = {
'username': 'John',
'password': 'Doe',
'csrfmiddlewaretoken': '123456712345671234567'
}
r = self.client.post('/login', data=data, headers=headers)
if r.status_code == 200:
return r.request.headers['Cookie']
else:
print "User cannot login"
#task
def progress(self):
self.client.get("/needed/authorization", headers = {"Cookie": self.cookie})