Flask Session loses key values - python

I have a trouble with Flask session. It loses values after redirect, here's code I'm using:
#app.route('/')
def index():
session['permanent'] = True
return flask.render_template('index.html') ##Here is template with form that passes value via GET to /station
#app.route('/station', methods=['GET'])
def station():
if 'st' not in session:
p = flask.request.args.get('Station', '')
session['st'] = p
print session['st']
if 'credentials' not in session:
return flask.redirect(flask.url_for('oauth2callback'))
credentials = client.OAuth2Credentials.from_json(session['credentials'])
if credentials.access_token_expired:
return flask.redirect(flask.url_for('oauth2callback'))
else:
##DO SOME STUFF WITH st and credentials##
return "Done!"
#app.route('/oauth2callback')
def oauth2callback():
flow = client.flow_from_clientsecrets('client_secret.json',scope='https://www.googleapis.com/auth/youtube',redirect_uri=flask.url_for('oauth2callback', _external=True))
if 'code' not in flask.request.args:
auth_uri = flow.step1_get_authorize_url()
return flask.redirect(auth_uri)
else:
auth_code = flask.request.args.get('code')
credentials = flow.step2_exchange(auth_code)
session['credentials'] = credentials.to_json()
return flask.redirect(flask.url_for('station'))
So what happens: Firts browser renders index.html. It's fine, user clicks something and passes the GET param to the station(). It goes thru the:
if 'st' not in session
session key is being set correctly. I see it in the print. Then user is being redirected to the /oauth2callback. There is whole auth process with youtube api and when script takes user back from /oauth2callback to /station via
return flask.redirect(flask.url_for('station'))
for some reason code enters the
if 'st' not in session
again. Like there was no session['st'] anymore. In the result I don't have the value from the form...
I have no clue what to do. (Using Flask 0.10 and hosting stuff on Heroku)

Related

Python Flask cookie not saving when tab closes unless tab is refreshed first

I am using Python Flask and Spotipy to create a web application that connects to the Spotify API.
When the user logs in with Spotify, I store the OAuth2 token as a cookie so that, if they refresh the tab or close it and reopen it later (but soon enough that the cookie doesn't expire), they will stay logged in.
The Issue: If the user logs in and then refreshes the tab, they will stay logged in for as long as the cookie is valid (no matter if they close the tab). However, if the user closes the tab (without first refreshing) and then reopens it, they will be logged out again and the cookie disappears. The same thing happens if the user opens a duplicate tab. As stated before, the user should stay logged in rather than getting logged back out.
Here is my Python Flask code:
#app.route('/')
def index():
access_token = ""
expire_at = 0
url = request.url
code = sp_oauth.parse_response_code(url)
if request.cookies.get('token') is not None:
access_token = request.cookies.get('token')
if code != url:
token_info = sp_oauth.get_access_token(code)
access_token = token_info['access_token']
expire_at += token_info['expires_at']
if access_token:
sp = spotipy.Spotify(access_token)
results = sp.currently_playing()
if results is not None:
resp = make_response(render_template("record.html", url=str(results['item']['album']['images'][1]['url']), title=str(results['item']['name']),artist=str(results['item']['artists'][0]['name'])))
else:
resp = make_response(render_template("record.html", title="Play A Song On Spotify", artist="To See It Appear Here!", url=str("http://www.clker.com/cliparts/m/I/n/1/T/P/orange-dot-hi.png")))
if request.cookies.get('token') is None:
resp.set_cookie('token', access_token, expires=expire_at)
return resp
else:
return render_template("home.html", button=getSPOauthURI())
If some other code is needed to find the issue, let me know.
Thanks in advance for the help!

Capture initial URL and Return to it after Azure SSO Authentication using MSAL for Python Dash App

I have put together my first dash app with the help of lots of online samples.
The app parses the URL for callbacks. The URL format is HTTP://www.app.root.com/ID/UID
If the user is already authenticated everything works fine, but if the user is not already authenticated the user is redirected for authentication via Azure SSO and then returned to the root URL.
How can I capture the initial URL entered and redirect to it after authentication?
#app.server.route(PATH_LOGIN)
def login():
"""
Start a login redirection
"""
# define a random id for a new session
session["state"] = str(uuid.uuid4())
# Technically we could use empty list [] as scopes to do just sign in,
# here we choose to also collect end user consent upfront
try:
session["flow"] = _build_auth_code_flow(scopes=SCOPE, state=session['state'])
# pylint: disable=broad-except
except Exception as ex:
msg = str(ex)
print(f"{func_name()}: {msg}")
session["flow"] = {}
print(f"{func_name()}: session['flow']: {session['flow']}")
return redirect(session["flow"].get("auth_uri"))
# Its absolute URL must match one of the app's redirect_uris set in the Azure Application Registry
#app.server.route(PATH_AUTHORIZATION)
def authorized() -> "Response":
"""
Process the response from the authorization mechanism (AAD: Azure Active Directory)
Returns:
The URL to use next, either to access the app or to retry the login.
"""
# useful for initial debugging
print(f"{func_name()}: request.args:\n {request.args}")
if request.args.get('state') != session.get("state"):
# return redirect(url_for("index")) # No-OP. Goes back to Index page
print(f"{func_name()}: Redirecting to Index: {request.args.get('state')} different than {session.get('state')}")
return redirect(url_for("login")) # Go back to Index page
if "error" in request.args:
# process Authentication/Authorization failure
print(f"{func_name()}: request.args: {request.args}")
session['auth_error'] = _tuples_to_dict(request.args)
print(f"{func_name()}: Authentication/Authorization failure: {session['auth_error']}")
return redirect(url_root() + PATH_ROOT)
if request.args.get('code'):
# process received authorization code
cache = _load_cache()
result = _build_msal_app(cache=cache).acquire_token_by_auth_code_flow(
session.get("flow", {}),
request.args
)
print(f"{func_name()}: user result: {result}")
if "error" in result:
session['auth_error'] = result
print(func_name(), result)
return redirect(url_root() + PATH_ROOT)
session["user"] = result.get("id_token_claims")
_save_cache(cache)
print(f"{func_name()}: Successful authorization")
return redirect(url_root() + PATH_ROOT)
I have tried adding this:
#app.server.route('/<subpath>')
def show_subpath(subpath):
print(subpath)
return subpath
And I think I need to use it as the "next_url" after "return redirect(url_for("login"))" and/or append it to the end of the return redirect(url_root() + PATH_ROOT), but it's not clear to me how to use the subpath value.
Thank you for any help!

I am getting key error when trying to access flask session data from another handler function

I am using dialogflow fulfillment flask package to build a simple chatbot. When I try to access the session variable in the def goalName(agent) handler that I set previously in the get_Name_ID(agent) handler, I get a key error message from the Heroku logs.
here is the webhook I am using:
#app.route('/webhook', methods=['POST', 'GET'])
def webhook() -> Dict:
"""Handle webhook requests from Dialogflow."""
# Get WebhookRequest object
request_ = request.get_json(force=True)
# Log request headers and body
logger.info(f'Request headers: {dict(request.headers)}')
logger.info(f'Request body: {request_}')
# Handle request
agent = WebhookClient(request_)
action = request_.get('queryResult').get('action')
if action == "get.secret.key":
agent.handle_request(get_Name_ID)
if action == "goal.setting.name":
agent.handle_request(goalName)
here is the first handler function
def get_Name_ID(agent):
task = TASK.query.filter_by(status="active").first()
if not USER.query.filter_by(passcode = agent.parameters["id"]).first():
user = USER(agent.parameters["id"], agent.parameters["name"])
db.session.add(user)
db.session.commit()
# store variables into session for later usage
key = id_generator()
user_session = SESSION(task.id, key)
db.session.add(user_session)
db.session.flush()
# store values to session
session['s_id'] = user_session.id
session['u_id'] = agent.parameters["id"]
session['user_name'] = agent.parameters["name"]
db.session.commit()
here is the second handler function:
def goalName(agent):
task = TASK.query.filter_by(status="active").first()
# print(type(redish.get('u_id')))
# print(redish.get('u_id'))
# get values from session
uid = session['u_id']
sid = session['s_id']
goal = GOAL(uid, task.id, sid, agent.parameters["goalName"], "", "", "", "", "")
db.session.add(goal)
db.session.flush()
session['goal_id'] = goal.id
db.session.commit()
I have setup the flask-session in the following manner:
app.config['SECRET_KEY'] = os.getenv('SECRET_KEY') or \
'e5ac358c-f0bf-11e5-9e39-d3b532c10a28'
app.config['SESSION_TYPE'] = 'sqlalchemy'
db = SQLAlchemy(app)
app.config['SESSION_SQLALCHEMY'] = db
sess = Session(app)
I have tried the following methods:
removing the flask-session package and using built-in flask session but with no success.
I have set up simple routes to test the session and it was working fine. But it fails to work within the handler functions.
I am getting the key error when accessing session data from the second handler:
_ 2021-08-05T10:47:48.928371+00:00 app[web.1]: return super().getitem(key) 2021-08-05T10:47:48.928372+00:00 app[web.1]: KeyError: 'u_id
I am not sure what is going on? Any help would be much appreciated!
You can use redis server for session. It Will be solved your issue

I got this error 'TypeError: can only concatenate str (not "NoneType") to str'

What I am trying to achieve is that the user will first ask the bot a question. Let say the user wants to find the nearest money changer he/she will type "I need to find a money changer. Then the bot will reply with 'Please provide the location". Once the user provides the coordinates the bot will then reply with all the nearby locations money changer.
from flask import Flask, request
import requests
from twilio.twiml.messaging_response import MessagingResponse
app = Flask(__name__)
#app.route('/sms', methods=['POST'])
def bot():
incoming_msg = request.values.get('Body', '').lower()
resp = MessagingResponse()
msg = resp.message()
if 'moneychanger' in incoming_msg:
search1 = 'Please provide the location please'
msg.body(search1)
message_latitude = request.values.get('Latitude', None)
message_longitude = request.values.get('Longitude', None)
responded = True
if message_latitude == None:
location = '%20' + message_latitude + '%2C' + message_longitude
responded = False
url = f'https://tih-api.stb.gov.sg/money-changer/v1?location={location}&radius=2000'
r = requests.get(url)
if r.status_code == 200:
data = r.json()
search = data['data'][0]['name']
else:
search = 'I could not retrieve a quote at this time, sorry.'
msg.body(search)
responded = True
return str(resp)
if __name__ == "__main__":
app.run(debug=True)
Twilio developer evangelist here.
I believe you are working with the Twilio API for WhatsApp, based on your using location parameters.
The issue here is that you are trying to reply to and receive more information within the same webhook request. However, the text message (with "moneychanger" in it) will come in a different request to the request with the location message. So, you need to store some state within your application that says your user is currently looking for a moneychanger.
Here's an example using Flask Sessions to store the incoming message and then ask for location and if there's a message put that together with the message and respond:
from flask import Flask, request, session
import requests
from twilio.twiml.messaging_response import MessagingResponse
app = Flask(__name__)
# Set the secret key to some random bytes. Keep this really secret!
# Don't use these bytes because they are in the documentation.
app.secret_key = b'_5#y2L"F4Q8z\n\xec]/'
#app.route('/sms', methods=['POST'])
def bot():
incoming_msg = request.values.get('Body', '').lower()
resp = MessagingResponse()
msg = resp.message()
message_latitude = request.values.get('Latitude', None)
message_longitude = request.values.get('Longitude', None)
if 'moneychanger' in incoming_msg:
# We're looking for a moneychanger, ask for the location
response = 'Please provide the location please'
session['message'] = incoming_msg
elif message_latitude && message_longitude && 'message' in session && 'moneychanger' in session['message']
# We have the location and the previous message was asking for a
# moneychanger.
location = '%20' + message_latitude + '%2C' + message_longitude
url = f'https://tih-api.stb.gov.sg/money-changer/v1?location={location}&radius=2000'
r = requests.get(url)
if r.status_code == 200:
data = r.json()
response = data['data'][0]['name']
else:
response = 'I could not retrieve a quote at this time, sorry.'
# we're done with the original message so we can unset it now.
session['message'] = None
else:
# In this case, either you have a message that doesn't include
# 'moneychanger' or you have latitude and longitude in the request but
# no session['message']. You probably want to do something else here, but
# I don't know what yet.
response = 'I\'m not sure what you\'re looking for.'
msg.body(response)
return str(resp)
if __name__ == "__main__":
app.run(debug=True)
You may also want to extend this so that if you receive a message with location in before receiving the request ('moneychanger') then you could store the location in the session and then ask what the user is looking for.
Let me know if that helps at all.

tornado app failing to set cookie after FB login with OAuth

I have written a tornado app which uses FB authentication. First it gets permission from the user & gets an access token. And then it makes another call to the FB with the access token, get the email id and save it in a cookie.
Following is the handler:
class FAuthLoginHandler(tornado.web.RequestHandler, tornado.auth.FacebookGraphMixin):
#tornado.web.asynchronous
def get(self):
user_id = self.get_secure_cookie('trakr')
if self.get_argument('code', None):
self.get_authenticated_user(
redirect_uri=settings.redirect_url,
client_id=self.settings['facebook_api_key'],
client_secret=self.settings['facebook_secret'],
code=self.get_argument('code'),
callback=self.async_callback(self._on_facebook_login))
return
elif self.get_secure_cookie('access_token'):
self.redirect('/')
return
self.authorize_redirect(
redirect_uri=settings.redirect_url,
client_id=self.settings['facebook_api_key'],
extra_params={'scope': 'email'}
)
def _on_facebook_login(self, user):
if not user:
self.clear_all_cookies()
raise tornado.web.HTTPError(500, 'Facebook authentication failed')
# print "http//graph.facebook.com/%s/picture" % str(user['id'])
# print "http//graph.facebook.com/%s" % str(user['id'])
self.set_secure_cookie('fb_id', str(user['id']))
self.set_secure_cookie('access_token', str(user['access_token']))
self.facebook_request("/me", access_token=user["access_token"], callback=self.async_callback(self._save_user_profile))
self.redirect('/')
def _save_user_profile(self, user):
if not user:
raise tornado.web.HTTPError(500, "Facebook authentication failed.")
# print user
print user.get('email')
self.set_secure_cookie('trakr', user.get('email'))
print self.get_secure_cookie('trakr')
Inside _save_user_profile, the first print statement prints the email id the user logged in. It successfully prints my email. Then I try to save that in the cookie, but when I print that I am getting response as None
I thought I messed with using classes & may be I was using wrong self. I printed id of self in several places, seems it's same. And I also printed dir(self) and it does have set_secure_cookie function. So why it is failing to set the cookie?
You're redirecting the user away before you have a chance to set the cookie. Remove the redirect call in _on_facebook_login and move it to the end of _save_user_profile.
Also, you can't read a cookie back immediately after you set it - setting cookies will take effect on the next request; the get_cookie APIs return the cookies that were present when the request came in.

Categories