After days of reading through the source code of Flask and Flask-Login and trying to figure out why my user sessions aren't being visibly accessible outside the route where the session is set, I am convinced that the response sent by Flask API is causing the issue.
I have tried to reverse engineer some of the tutorials that are present on the internet to use Flask-Login, however, most of them rely on using Jinja templates. This posed a challenge as I had to figure out a way to make it work with a React frontend. I initially used the examples with jinja templates as an example as I was building out the React frontend and I was immediately faced with some issues.
I have the following routes: /login and /logout. I also utilized current_user from Flask-Login to access session details. So for example:
If the /login route is accessed and current_user is authenticated, then nothing happens.
If the /login route is accessed and current_user is not authenticated, then the authentication logic is run, session is now updated to include the authenticated user and user is logged in
If the /logout route is accessed and current_user is not authenticated, nothing happens
If the /logout route is accessed and current_user is authenticated, then the session is cleared and used is logged out
I was able to get the first two from above to work, however, when the user is logged in, the session appears to not been saved and I am not able to log out.
Below are two different code snippets:
Jinja Templates
#auth.route('/login')
def login():
return render_template('login.html')
#auth.route('/login', methods=['POST'])
def login_post():
email = request.form.get('email')
password_hash = request.form.get('password')
user = RegisteredUser.query.filter_by(email=email).first()
if user is not None and user.check_password(password_hash):
login_user(user)
return redirect(url_for('main.profile'))
return redirect(url_for('auth.login'))
JSON Object Response
#auth.route('/login', methods=['GET', 'POST'])
def login_post():
if current_user.is_authenticated:
return jsonify({ 'login' : True })
data = request.get_json()
user = RegisteredUser.query.filter_by(email=data['user']).first()
if user is not None and user.check_password(data['password']):
login_user(user)
return jsonify({ 'login' : True })
return jsonify({ 'login' : False })
The code example using Jinja templating works and the session is saved after all the code is finished executing when the /login route is accessed. However, this is not the case for the code returning JSON data.
This leads me to believe that in order to properly use Flask-Login, developers would need to make custom Flask Response objects (i.e. look like - redirect(url_for(...)) or render_template(...)) and reconstruct how the response would look like if Jinja templating was used.
I was wondering if anyone else had experienced this or is there a different path I could take that would be better.
Thanks.
Edit:
I am now open to different possible solutions. If I don't use flask-login, what are other options for user session management for React and Flask?
I have looked into flask-security, flask-user, flask-session, but they don't seem to be maintained as much as flask-login.
I am ideally looking at cookie-based solutions.
For my solution, I scrapped using Flask-Login and went another path.
Since what I did may unintentionally reveal confidential stuff, I will provide a high-level overview. Feel free to ask questions as I have saved all the resources that led me to figuring out what to do.
I decided to use Google's Firebase API (For the frontend) and Admin SDK (For the backend).
Admin SDK uses session cookies, so if you have two separate servers here are some topics that you might want to look into
Cross-origin resource sharing
Cross-domain cookies
Subdomains (This relates to the point above)
Because I am using cookies, something that I had to be mindful of is CSRF attacks. For this, I am using Flask-WTF extension because it has an implementation to prevent it.
However, in the event of any errors such as:
CSRF token is missing
CSRF session token is missing
CSRF session token expired
etc
Flask-WTF by default doesn't return anything back to the client (at least if running two separate servers). When an error occurs, it just displays a 4XX page. As a result, I forked the repository source code such that I can get some kind of 4XX response to the client.
I am not a security expert by any means, but I have learnt quite a bit of small things along the way. Be sure that a secure connection is always in place. Also, make sure when you are setting up subdomains, be very careful as you don't want to fall victim to subdomain takeovers.
Cheers!
Related
I am using Flask,Python for my web application . The user will login and if the session time is more than 5 minutes then the app should come out and it should land on the login page.
I tried some of the methods and I can see the session time out is happening but redirect to login page is not happening.
#app.before_request
def before_request():
"Session time out method"
flask.session.permanent = True
app.permanent_session_lifetime = datetime.timedelta(minutes=2)
flask.session.modified = True
flask.g.user = flask_login.current_user
#return redirect(url_for('login'))
I have used before_request for seesion time out.
I have referrred this link Flask logout if sessions expires if no activity and redirect for login page
but I dont see any changes from what I have tried before and this code. I can see lot of stackoverflow questions over there for this topic and I couldnt find the solution.
I have tried this link als
Expire session in flask in ajax context
But I am not sure what should I pass as a session and what qualifier I should return here?
#mod.before_request
def make_session_permanent():
if session_is_invalid(session):
return redirect(url_for('logout'))
def session_is_invalid(ses):
# return your qualifier
if the previous method is correct can some one tell me what is the session and what qualifier I should return here?
What I need is after session log out the page should automatically land on the login screen
What is happening is session log out is happening but it's not redirecting to login page
could some one help me in this?
When I read the documents of the Flask-Login package, i saw a few things. When creating your Flask application, you also need to create a login manager.
login_manager = LoginManager()
A login_view variable in LoginManager class caught my attention. The details include the following explanation:
The name of the view to redirect to when the user needs to log in. (This can be an absolute URL as well, if your authentication machinery is external to your application.)
Actually, after you create a LoginManager object,
login_manager.login_view = 'your login view'
You should specify your login page. Finally,
Once the actual application object has been created, you can configure it for login with:
login_manager.init_app(app)
After doing these, any unauthorized calls to every method you use #login_required annotation will be sent to the page you have pointed out with login_view.
I also developed a simple application. You can review this application from here. I tested it and working without any problems.
I hope it helps you.
I successfully completed my web app with Flask ad deployed it on my server. I use current_user in some of my methods and I need that information for some purposes. Lately I noticed a problem. There was already logged in pages and at that point I restarted flask server. Logged in pages were untouched during that. And current_user became anonymous in all open and previously logged in pages. To prevent that, I tried such:
#app.before_request
def make_session_permanent():
session.permanent = True
But still the same problem What should I have done? At least I would like to force all client pages to go to login page after server restart.
with app.secret_key = os.urandom(12)I was using a changing secret key. I did it so that app.secret_key = 'sansal54' and it was solved.
test.html:
authorize
views.py:
from django.contrib.auth.decorators import login_required
#login_required
def myview(req):
user = req.user
return render(req, 'test.html')
For ebay's oauth process, you have to provide users with a link to ebay's server, which asks the user if they want to give credentials to you. If they accept, ebay redirects the user to a given url with a querystring containing the access key.
The problem is, when I authorize my app with ebay, the user gets redirected to my login page (despite already being logged in). If I remove the #login_required decorator, req.user returns AnonymousUser instead. This is a problem since I don't know which user to assign the access token to.
What am I missing here?
Note that I am using ngrok to tunnel my server, and I have no problems
rendering myview other than the fact that the user is Anonymous.
The problem was that when I logged the user in initially, I was using the domain localhost:8000 instead of my ngrok instance.
Logging my user in using my ngrok address fixed the problem.
I have been following this Flask pyjwt guide, however my web app is somewhat similar to Miguel's microblog example that uses render_template() and redirect(url_for(...)) for navigation.
I have implemented an encoding and decoding service in my application, however I do not know how to properly return the encoded JWT token to the user using redirect()
My code is as follows:
#app.route('/', methods=['GET', 'POST'])
def login():
login_form = LoginForm()
username = login_form.username.data
password = login_form.password.data
if is_user_valid(username, password):
return redirect(url_for('home'), auth_service.encode_token(username))
render_template('login.html', form=login_form)
My problem is placing the auth token inside the redirect method causes a page to appear saying "Redirecting... you should be redirected, if not click here", which I do not want. I do not particularly wish to change my redirect methods to something similar to make_response(jsonify(...)) as I would then need to handle these responses in the front end when this is a simple login page.
How should I be returning the auth token to the client correctly?
Typically you attach it to response headers which is what your redirect method returns.
response = redirect(url_for('home'))
response.headers['X-JWT-TOKEN'] = auth_service.encode_token(username)
return response
However, I'm not sure if this is gonna be very useful in your setup when using render_template, because you can not easily access these headers on the frontend.
You have two alternatives here:
Make your login endpoint an API. This way you can return the token for the client-side code to handle and store it somehow.
Drop the JWT and stick to using sessions. I know that this is not what you expected, but outside of building APIs JWT-based auth is not very helpful.
This question already has an answer here:
Store post data for use after authenticating with Flask-Login
(1 answer)
Closed 6 years ago.
I'm using flask-login to handle authentication for my app, which is an API that expects HTTP Basic Authorization headers as part of each request (so the user can login and make the request without worrying about sessions, cookies, or having to do the login and requesting in separate steps).
My requests are working out like this:
POST /api/group/48
GET /login?next=%2Fapi%2Fgroup%2F48
GET /api/group/48
That is, a POST request to /api/group/48 is getting intercepted and redirected to the /login endpoint (as expected). What happens in /login is not interactive - it takes the Basic Authorization header and logs the user in.
After the login has completed, the client is redirected back to /api/group/48 - but this time as a GET request, not a POST. And in this app, the /api/group/48 endpoint is expecting only POST data, so it dies with a 405 (Method not allowed) error.
Is this the expected behavior of flask-login? How can I have it pass through the POST request as originally submitted? (or alternatively, should I be using some different architecture so that the redirect to /login, then back to /api/group/48 doesn't take place and the POST data isn't lost?)
I haven't included code, since I don't think this is a code-specific issue. But if it turns out I'm doing something wrong, I can post some sample code.
Thanks to anyone who can help.
This is more then a redirect (which also might still be problematic for some browsers / clients). Your client would need to remember the body of the initial request, and re-post it later. No such mechanism exists in HTTP specification.
What you can do is remember the request (in session on something similar), then do a redirect, and then process the stored request. I don't think there are any shortcuts here.
If you really need to redirect as POST, you can do this.
Suppose you have a view with redirect like this (taken shamelessly from flask-login docs):
#app.route('/login', methods=['GET', 'POST'])
def login():
# Here we use a class of some kind to represent and validate our
# client-side form data. For example, WTForms is a library that will
# handle this for us, and we use a custom LoginForm to validate.
form = LoginForm()
if form.validate_on_submit():
# Login and validate the user.
# user should be an instance of your `User` class
login_user(user)
flask.flash('Logged in successfully.')
next = flask.request.args.get('next')
# next_is_valid should check if the user has valid
# permission to access the `next` url
if not next_is_valid(next):
return flask.abort(400)
return flask.redirect(next or flask.url_for('index'))
return flask.render_template('login.html', form=form)
Then as this answer quotes Wikipedia:
Many web browsers implemented this code in a manner that violated this
standard, changing the request type of the new request to GET,
regardless of the type employed in the original request (e.g. POST).
1 For this reason, HTTP/1.1 (RFC 2616) added the new status codes
303 and 307 to disambiguate between the two behaviours, with 303
mandating the change of request type to GET, and 307 preserving the
request type as originally sent.
So your answer is change redirect to flask.redirect(next or flask.url_for('index'), code=307)
Also note that you'll probably need to redirect to login view as POST also to make it work. You can make your custom unauthorized handler:
#login_manager.unauthorized_handler
def unauthorized():
# do stuff
return a_response