Authenticating username and passwords, i realised that if the developer is not careful we could land with a netflix effect. multiple users joining with one login details
Say you want to restrict exclusive access to your users but they are very kind and share their login details to others .
So I thought in an alternative way, the following are the steps:
Login page - > generic landing page
if username and password are not in records → go to generic landing page
if username and password match records:
Generate a random string of characters and save in your username records, AND send the user to an url weblink with an address like… “www.webapp/username/random_string/”
Username lands back to the page with “webpage/username/random_string”.
with a callback with Input(‘url’, ‘pathname’), read the pathname, split by “/” and catch the username, and the random key
check in the register that username has that specific key in your records and send to the customised page
in that way, if the users shares their login details to others, there wont be a chance of having two users at the same time
Another alternative option is a “passwordless” option. Sending automatic emails with a weblink that includes an specific created and saved string of random characters (which the app will check to match the registry once the users clicks on the link in their email, sending the user to the landing page with username and the random key). This case can be very simplistic and very safe if you think about it, user can only indicate their username and the app sends an automatic emails with a random key links for access. In this case security is passed on whichever email company the users has. Since the random key is generated every time a user gets in this option is very safe.
Using any of those methods, or flask_login by itself will not keep from people doing things to share the logins.
For example, someone could always forward the email that you are sending for the login or do other things. The default session cookie could also be shared and bypass the info. Thus why making sure your cookies are secure is a big thing.
Honestly, if you are wanting to have a subscription based product and using logins and you are concerned with people sharing the app, this is where your legal team needs to step in to make a:
privacy policy
end user license agreement
need to make sure there is a cookie consent
On top of this, because the user has accepted these things, and they use the app in a manner that is not along the agreement, then you can disable their account.
You will also need to be additionally monitoring their account for any activity outside of the norm. Locations aren’t in the same area, etc.
This is all if you mind people sharing their login.
You could also have something in there which limits the sessions to something like 3 to 5. Microsoft uses this for its licenses as well, so might be a good starting point. This would allow people to log in on their phone and computer and one other device.
You would also need a mechanism that checks whether their session should be logged out.
This code is super helpful. I was able to lock down some pages and still leave some for anonyms user to access.
One thing I noticed:
I have NavLinks on the side which change the page of my multi pages app using the pages feature.
Even tough, I have dcc.Location with refresh and Navlinks with refresh, I won’t get a request I can intercept when switching between pages.
The idea is, that I have a second page, and if the user is not registered when switching from page 1 to page 2, he should get redirected to login.
Are the dash pages not routed through the request process?
Does the redirect work the same way with the Get request?
What i did looked like this:
@app.before_request
def check_login():
block_list = ["/composition"]
# Check if the requested route is whitelisted
if request.method == "GET":
if current_user:
if request.path not in block_list:
return
elif request.path in block_list and not current_user.is_authenticated:
return redirect(url_for("auth_bp.login"))
else:
for pg in dash.page_registry:
if request.path == dash.page_registry[pg]["path"]:
session["url"] = request.url
return
return
else:
if current_user:
data = json.loads(request.data)
# Check if the request data contains 'inputs' with a 'pathname' attribute
inputs = data.get('inputs', [])
if len(inputs) == 1 and inputs[0].get('id', False) == 'url' and inputs[0].get('value', False) in block_list and not current_user.is_authenticated:
# The page redirect goes to block list and user is not authenticated, redirect
session["url"] = request.url
return redirect(url_for("auth_bp.login"))
else:
for pg in dash.page_registry:
if request.path == dash.page_registry[pg]["path"]:
session["url"] = inputs[0].get('value', False)
return
return jsonify({"status": "401", "statusText": "unauthorized access"})
HI @jinnyzor , Somehow I ended at this Problem again.
My issue is:
The session cookie expires and the user is set to anonymous with flask_login. In my before request I also see it, but as long as the request is coming from within dash, for example clicking a button, then the redirect does not work.
@app.before_request
def handle_before_request():
if request.method == "GET":
found = ("admin" in request.path) and ("static" not in request.path)
if current_user.is_authenticated:
if found:
if current_user.is_admin:
from src.helpers import save_last_page_visited
save_last_page_visited(request.path)
if not current_user.is_admin and request.path in [
current_app.config["URL_PAGE_1"],
current_app.config["URL_PAGE_2"],
]:
flash(
"This page is for admins only. Please Sign in as admin so you can check this page!",
category="warning",
)
return redirect("/login")
else:
if current_user.is_anonymous and request.path == "/_dash-update-component":
#Here should the user be redirected to login page
# return jsonify({"multi": True, "response": {"url": {"pathname": "/login"}}})
return redirect("/login")
The login flow is done in flask, where the user can either use his account made on my page, or google. I store the user in the session on login. That all works well.
If I use neither works for me return jsonify({"multi": True, "response": {"url": {"pathname": "/login"}}}) or redirect("/login").
Do you know how I can redirect the user correctly?
Yes , I had the else statement there before. Need to check it also because we have some URL outside the protected area, like redirecting to google Oauth and coming back.
In the post request, I want to redirect the user, as long as he is not logged in, most likely he will be within dash. The issue was, that the user stayed on one page and the session timed out. Now he is pressing a button (not a link to a different page) and nothing happens because he is not logged in any more. In that case, I want to redirect him.
Yes, I have the URL as a component. The login URL is outside of dash and handled by flask. Similar as in your example above.
Hi there @jinnyzor,
thank you for the contribute to the plotly community.
I have managed to implement your login flow outside dash, now what is the best way to pass user data to the page when the user is authenticated and load different experiences based on the user level og authority. I already set up user permission levels and user retrieving.This is my login:@server.route(‘/login’, methods=[‘POST’, ‘GET’])
def login(message=“”):
if request.method == ‘POST’:
if request.form:
username = request.form[‘username’]
password = request.form[‘password’]
# Authenticate user against database
result, user, authority, tenants, projects = db_access.db.Get_permission(username, password)
if result == 0: # Login success
login_user(user)
return redirect('/')
else:
message = "invalid username and/or password"
else:
if current_user:
if current_user.is_authenticated:
return redirect('/')
return render_template('login.html', message=message). Thank you in advance for your help!
The way you access the user is by using the current_user of flask login. You’ll need to make sure you handle if there isn’t a request for things like a page layout and things to make sure your layout can pass the validation process.
Thanks for the reply @jinnyzor.
So for example if i have my home and navbar,to render the elements inside them conditionally based on current_user i need to wrap the layout in a function and then pass it to the main layout like:
App.layout =[navbar_layout(),home_layout()]
This will cause the layout to be rendered when user is found?