In my Flask API, I'm trying to get sessions to stay permanent, so that it will still be there even after browser closes. My front-end is written in React, and uses the Fetch API to make requests. However, after testing out what I have so far, it doesn't seem to work. My code (left out some irrelevant database stuff that works):
#bp.route('/login', methods=('POST',))
def login():
...
error=None
res = {}
db = get_db()
cursor = db.cursor(dictionary=True)
...
user = cursor.fetchone()
...
if error is None:
session.clear()
session.permanent = True
session['userID'] = user['id']
current_app.logger.debug(session['userID'])
res['actionSuccess']= True
else:
res['actionSuccess'] = False
res['error'] = error
return jsonify(res)
So far, I can tell that sessions is indeed storing the userID value. I then write another route to tell me if an userID value is stored in session, like so:
#bp.route('/init', methods=('GET',))
def init():
userID = session.get('userID')
if userID is None:
res = {"signedIn": False}
else:
res = {"signedIn": True, "username": userID}
return jsonify(res)
However, everytime I make the call to '/init', it returns False even though I previously signed in. I don't know why session isn't permanent here. Is it because I'm running the client locally on my machine? Do I need to allow cookies somewhere on my Chrome browser? I used Chrome to look into the cookies stored for the client, and no "sessions" was stored there. Do I have to something extra on the front-end to store the cookies/session, or do they store automatically? Am I misunderstanding the usage of Flask sessions?
Found out why sessions wasn't working after a lot of research! Flask sessions are essentially cookies, and I was using the Fetch API to perform CORS operations. Fetch() by default does not allow cookies to be received or sent, and must be configured in order to use Flask sessions.
On my React.js client, I did this by setting 'include' for 'credentials':
fetch(url, {
method: 'POST',
mode: 'cors',
body: JSON.stringify(loginObj),
credentials: 'include',
headers: {
'Content-Type': 'application/json'
}
})
.then(res => res.json())
...
Because of this configuration, the request isn't considered a "simple request", and the client will actually "preflight" the POST request with an OPTIONS request. This means that before my POST request is sent, a OPTIONS request testing to see if the server has the correct access will be sent to my Flask server first.
The preflight OPTIONS request will test to see if the response from the server has the correct headers containing "Access-Control-Allow-Origin", 'Access-Control-Allow-Credentials', and 'Access-Control-Allow-Headers'. If the test sent by the OPTIONS request fails, the actual POST request will not be sent and you'll get a Fetch error.
I then set the headers accordingly on my Flask server like so:
#bp.route('/login', methods=('POST','OPTIONS'))
def login():
if request.method == 'OPTIONS':
resp = Response()
resp.headers['Access-Control-Allow-Origin'] = clientUrl
resp.headers['Access-Control-Allow-Credentials'] = 'true'
resp.headers['Access-Control-Allow-Headers'] = "Content-Type"
return resp
else:
'''
use session for something
'''
res['actionSuccess'] = False
js = json.dumps(res)
resp = Response(js, status=200, mimetype='application/json')
resp.headers['Access-Control-Allow-Origin'] = clientUrl
resp.headers['Access-Control-Allow-Credentials'] = 'true'
resp.headers['Access-Control-Allow-Headers'] = "Content-Type"
return resp
Take note that 'Access-Control-Allow-Credentials' was set to 'true' as opposed to the Python boolean True, as the client will not recognize the Python boolean.
And with that, a Flask Session object should be stored in your cookies.
Related
I have the following configuration for flask:
app.config['SESSION_REFRESH_EACH_REQUEST'] = False
app.config['SESSION_PERMANENT'] = True
I understood that since 'SESSION_REFRESH_EACH_REQUEST' is False, it will not send set-cookie in the response when session is not modified.
In the after_request I check if session was modified:
#app.after_request
def add_header(response):
if session.modified == False:
print(response.headers)
return response
It indeed wasn't!
No matter what I do, even when I explicit set session.modified = False, flask response have a set-cookie header with the session ID.
I need flask to not send set-cookie in the response because I want to connect my website with cloudflare, caching every response. How can I do it?
To remove the behavior of sending set-cookies in http header, I modified the "should_set_cookie" function of session interface, right after you create you app instance:
from flask.sessions import SecureCookieSessionInterface, SessionMixin
class CustomSessionInterface(SecureCookieSessionInterface):
def should_set_cookie(self, app: "Flask", session: SessionMixin) -> bool:
if (session.modified == False and request.method == 'GET'):
return False
else:
return True
app.session_interface = CustomSessionInterface()
This made sure it will not set-cookies if the session wasn't modified and is a GET request.
This is helping me a lot in cloud caching!
Whenever I am logged into a django user and I try and send a PUT request to my URL I get a 403 Forbidden Error. However, it works both when I am not logged in and also from the Django Rest API client.
Here is my code in my frontend:
let parameters = `user/${userID}/`
return new Promise((resolve, reject) => {
axios({
method: 'PUT',
url: 'http://127.0.0.1:8000/' + parameters,
data: updatedUser,
headers: {
'Content-Type': 'application/json',
},
})
.then((response) => {
resolve(response)
})
.catch(error => {
console.log(error)
// reject(error)
})
});
I am very confused as I can't see the difference when I am logged into a django user and when I am not, as nothing changes in the frontend. Thanks
EDIT:
This is in my urls.py
path('user/<id>/', views.RetrieveUpdateDestroyUser.as_view()),
And this is the view:
class RetrieveUpdateDestroyUser(RetrieveUpdateDestroyAPIView):
"""
View to handle the retrieving, updating and destroying of a User.
This View will also log any changes made to the model.
"""
serializer_class = UserCreateUpdateSerializer
queryset = CustomUser.objects.all()
lookup_field = 'id'
permission_classes = (AllowAny,)
def update(self, request, *args, **kwargs):
"""
PUT and UPDATE requests handled by this method.
"""
return super().update(request, *args, **kwargs)
I have also tested doing POST and PUT when I am logged into a user and they don't work, but GET does. Thanks
Also tried disabled CSRF but to no avail either
Writing this answer to summarize what we have discovered.
The problem: the AJAX (PUT) call to the DRF endpoint fails with 403 HTTP error for authenticated users and works just fine for anonymous users
Desired Behaviour: make that call working for both anonymous and authenticated users
Reason: by default DRF perform CSRF check for unsafe HTTP methods (POST, PUT, PATCH and DELETE) https://www.django-rest-framework.org/topics/ajax-csrf-cors/
Possible Solutions:
Disable CSRF check like described here https://stackoverflow.com/a/30875830/764182
Pass CSRF token within the PUT request. For more information about CSRF + AJAX in Django read here https://docs.djangoproject.com/en/3.1/ref/csrf/#ajax. For Axios and default Django settings the solution might be:
axios.defaults.xsrfHeaderName = "X-CSRFTOKEN";
axios.defaults.xsrfCookieName = "csrftoken";
axios.defaults.withCredentials = true;
I want to set up CORS within my flask application and adding flask-cors helped up to a certain point. While "CORS(app)" helped with all routes that don't require Authorization, I can't access routes that do.
One route in question looks like this.
#users_blueprint.route('/users/<user_id>', methods=['GET'])
#authenticate
def get_single_user(resp, user_id):
response_object = {
'status': 'failure',
'message': 'Invalid Payload'
}
if resp != user_id:
response_object['message'] = 'You are not authorized to view this user'
return jsonify(response_object), 400
try:
user = User.query.filter_by(user_id=user_id).first()
if user:
response_object['status'] = 'success'
response_object['data'] = user.to_json()
return jsonify(response_object), 200
else:
response_object['message'] = 'The user does not exist'
return jsonify(response_object), 400
except (exc.IntegrityError, ValueError, TypeError) as e:
db.session().rollback()
return jsonify(response_object), 400
I played around with the CORS settings and so far got this:
#enable CORS
CORS(app, allow_headers=["Content-Type", "Authorization", \
"Access-Control-Allow-Credentials"], supports_credentials=True)
But when I try to access the route in question from within my React application via fetch I get this error:
Failed to load
MYROUTE: No
'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'http://localhost:3000' is therefore not allowed
access. The response had HTTP status code 502. If an opaque response
serves your needs, set the request's mode to 'no-cors' to fetch the
resource with CORS disabled.
What else could I do?
EDIT
When I send a OPTIONS request via CURL I can see that my route in question will respond with "access-control-allow"-headers. So I'm really clueless as to what is going on. Here's also my fetch request:
const url = `myurlendpoint/myuserid`
const token = my_id_token
const options = {
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
};
fetch(url, options)
.then(response => console.log(response.json()))
.catch(error => console.log(error))
ALSO: When I call my route without the header 'Authorization' present I get the correct response saying "No auth provided" and NOT the cross-origin problem. If this route really had not cross-origin-allow set then it should state that in my request without Authorization, right? So it has something to do with the Authorization header...
I'm trying to build a serverless Flask APP. To login users, I use auth0.com.
After the user logs in I get an access token, I send a post request with it to my flask backend and there I exchange the token for the user info doing this:
#app.route('/callback', methods=['POST'])
#cross_origin()
def callback_handling():
resp = request.get_json()
url = 'https://' + AUTH0_DOMAIN + '/userinfo'
headers = {'authorization': 'Bearer ' + resp['access_token']}
r = requests.get(url, headers=headers)
userinfo = r.json()
# Store the tue user information in flask session.
session['jwt_payload'] = userinfo
session['profile'] = {
'user_id': userinfo['sub'],
'name': userinfo['name'],
'picture': userinfo['picture']
}
Once I've done this I redirect the user to their dashboard. There I send a second post request to fetch the user profile, something like this:
#app.route('/profile', methods=['POST'])
#cross_origin()
def user_profile():
if 'profile' in session:
return jsonify({'profile':session['profile']})
else:
return jsonify({'profile':"Not logged in"})
This second function returns always {'profile':"Not logged in"}.
So I'm wondering what's the best way to do this. Should I always send back the auth0 token, send a request to them to ask who is the user and then return his data? It seems like an overkill to send always a request to auth0 everytime I need to return some data. Is there a better method?
I have my rest-api set up in Django and am using React Native to connect with it. I have registered users and am able to generate tokens however I am unable to pass the token in the header of the GET request. My code is as follows:
try{
let response = await fetch("http://127.0.0.1:8000/fishes/auth/",
{
method: 'GET',
headers: {
// 'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': ' Token '+accessToken,
}});
let res = await response.text();
}}
I have been following this link http://cheng.logdown.com/posts/2015/10/27/how-to-use-django-rest-frameworks-token-based-authentication and have already verified that the response from the rest api is correct.
However on the phone with native react I get the following error in the console:
TypeError: Network request failed
at XMLHttpRequest.xhr.onerror (fetch.js:441)
at XMLHttpRequest.dispatchEvent (event-target.js:172)
at XMLHttpRequest.setReadyState (XMLHttpRequest.js:542)
What am I doing wrong in the GET code?
Alright 401 status code which means UnAuthorized.
For Django Rest Framework you must pass in the access Token as part of header for all your API requests.
Header Format will be
Key : Authorization
Value: Token <token>
You can see more here
http://www.django-rest-framework.org/api-guide/authentication/#tokenauthentication
I think that you need change
'Content-Type' to 'content-type'
On lowercase
See This answer.
The same-origin policy restricts the kinds of requests that a Web page can send to resources from another origin.
In the no-cors mode, the browser is limited to sending “simple” requests — those with safelisted methods and safelisted headers only.
To send a cross-origin request with headers like Authorization and X-My-Custom-Header, you have to drop the no-cors mode and support preflight requests (OPTIONS).
The distinction between “simple” and “non-simple” requests is for historical reasons. Web pages could always perform some cross-origin requests through various means (such as creating and submitting a form), so when Web browsers introduced a principled means of sending cross-origin requests (cross-origin resource sharing, or CORS), it was decided that such “simple” requests could be exempt from the preflight OPTIONS check.
I got around this issue by handling preflight request, as stated by the OP.
Previously, in my middleware, I filtered out requests that did not include an auth token and return 403 if they were trying to access private data. Now, I check for preflight and send a response allowing these types of headers. This way, when the following request comes (get, post, etc), it will have the desired headers and I can use my middleware as originally intended.
Here is my middleware:
class ValidateInflight(MiddlewareMixin):
def process_view(self, request, view_func, view_args, view_kwargs):
assert hasattr(request, 'user')
path = request.path.lstrip('/')
if path not in EXEMPT_URLS:
logger.info(path)
header_token = request.META.get('HTTP_AUTHORIZATION', None)
if header_token is not None:
try:
token = header_token
token_obj = Token.objects.get(token=token)
request.user = token_obj.user
except Token.DoesNotExist:
return HttpResponse(status=403)
elif request.method == 'OPTIONS':
pass
else:
return HttpResponse(status=403)
Here is my Options handling
class BaseView(View):
def options(self, request, *args, **kwargs):
res = super().options(request, *args, **kwargs)
res['Access-Control-Allow-Origin'] = '*'
res['Access-Control-Allow-Headers'] = '*'
return res