python/flask find unique ids for each website visitor [duplicate] - python

I want to build a simple webapp as part of my learning activity. Webapp is supposed to ask for user to input their email_id if it encounters a first time visitor else it remembers the user through cookie and automatically logs him/her in to carry out the functions.
This is my first time with creating a user based web app. I have a blue print in my mind but I am unable to figure out how to implement it. Primarily I am confused with respect to the way of collecting user cookie. I have looked into various tutorials and flask_login but I think what I want to implement is much simpler as compared to what flask_login is implementing.
I also tried using flask.session but it was a bit difficult to understand and I ended up with a flawed implementation.
Here is what I have so far (it is rudimentary and meant to communicate my use case):
from flask import render_template, request, redirect, url_for
#app.route("/", methods= ["GET"])
def first_page():
cookie = response.headers['cookie']
if database.lookup(cookie):
user = database.get(cookie) # it returns user_email related to that cookie id
else:
return redirect_url(url_for('login'))
data = generateSomeData() # some function
return redirect(url_for('do_that'), user_id, data, stats)
#app.route('/do_that', methods =['GET'])
def do_that(user_id):
return render_template('interface.html', user_id, stats,data) # it uses Jinja template
#app.route('/submit', methods =["GET"])
def submit():
# i want to get all the information here
user_id = request.form['user_id']# some data
answer = request.form['answer'] # some response to be recorded
data = request.form['data'] # same data that I passed in do_that to keep
database.update(data,answer,user_id)
return redirect(url_for('/do_that'))
#app.route('/login', methods=['GET'])
def login():
return render_template('login.html')
#app.route('/loggedIn', methods =['GET'])
def loggedIn():
cookie = response.headers['cookie']
user_email = response.form['user_email']
database.insert(cookie, user_email)
return redirect(url_for('first_page'))

You can access request cookies through the request.cookies dictionary and set cookies by using either make_response or just storing the result of calling render_template in a variable and then calling set_cookie on the response object:
#app.route("/")
def home():
user_id = request.cookies.get('YourSessionCookie')
if user_id:
user = database.get(user_id)
if user:
# Success!
return render_template('welcome.html', user=user)
else:
return redirect(url_for('login'))
else:
return redirect(url_for('login'))
#app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
# You should really validate that these fields
# are provided, rather than displaying an ugly
# error message, but for the sake of a simple
# example we'll just assume they are provided
user_name = request.form["name"]
password = request.form["password"]
user = db.find_by_name_and_password(user_name, password)
if not user:
# Again, throwing an error is not a user-friendly
# way of handling this, but this is just an example
raise ValueError("Invalid username or password supplied")
# Note we don't *return* the response immediately
response = redirect(url_for("do_that"))
response.set_cookie('YourSessionCookie', user.id)
return response
#app.route("/do-that")
def do_that():
user_id = request.cookies.get('YourSessionCookie')
if user_id:
user = database.get(user_id)
if user:
# Success!
return render_template('do_that.html', user=user)
else:
return redirect(url_for('login'))
else:
return redirect(url_for('login'))
DRYing up the code
Now, you'll note there is a lot of boilerplate in the home and do_that methods, all related to login. You can avoid that by writing your own decorator (see What is a decorator if you want to learn more about them):
from functools import wraps
from flask import flash
def login_required(function_to_protect):
#wraps(function_to_protect)
def wrapper(*args, **kwargs):
user_id = request.cookies.get('YourSessionCookie')
if user_id:
user = database.get(user_id)
if user:
# Success!
return function_to_protect(*args, **kwargs)
else:
flash("Session exists, but user does not exist (anymore)")
return redirect(url_for('login'))
else:
flash("Please log in")
return redirect(url_for('login'))
return wrapper
Then your home and do_that methods get much shorter:
# Note that login_required needs to come before app.route
# Because decorators are applied from closest to furthest
# and we don't want to route and then check login status
#app.route("/")
#login_required
def home():
# For bonus points we *could* store the user
# in a thread-local so we don't have to hit
# the database again (and we get rid of *this* boilerplate too).
user = database.get(request.cookies['YourSessionCookie'])
return render_template('welcome.html', user=user)
#app.route("/do-that")
#login_required
def do_that():
user = database.get(request.cookies['YourSessionCookie'])
return render_template('welcome.html', user=user)
Using what's provided
If you don't need your cookie to have a particular name, I would recommend using flask.session as it already has a lot of niceties built into it (it's signed so it can't be tampered with, can be set to be HTTP only, etc.). That DRYs up our login_required decorator even more:
# You have to set the secret key for sessions to work
# Make sure you keep this secret
app.secret_key = 'something simple for now'
from flask import flash, session
def login_required(function_to_protect):
#wraps(function_to_protect)
def wrapper(*args, **kwargs):
user_id = session.get('user_id')
if user_id:
user = database.get(user_id)
if user:
# Success!
return function_to_protect(*args, **kwargs)
else:
flash("Session exists, but user does not exist (anymore)")
return redirect(url_for('login'))
else:
flash("Please log in")
return redirect(url_for('login'))
And then your individual methods can get the user via:
user = database.get(session['user_id'])

Related

Python-Flask render_template for user in session

I've made a successfull login to a html page (lets call it page1). Now I need to allow access to a second page (page2) only if the user has previously done the login. I think I need an IF to specify to which page the user is attempting to go: page1 or page2.
This is what I got:
#app.route('/user')
def user():
if "user" in session:
user = session["user"]
return render_template("page1.html")
else:
return redirect(url_for("login"))
This following is wrong, I need that IF:
def user():
if "user" in session:
user = session["user"]
if ... : # user attempting to go to page 1
return render_template("page1.html")
else:
return render_template("page2.html")
else:
return redirect(url_for("login"))
Thanks to all
Edited, to share the login method:
#app.route('/login', methods = ["GET","POST"])
def login():
error = None;
if request.method == "POST":
user = request.form["email"]
with open("users.txt", "r") as file:
file_reader = csv.reader(file)
for row in file_reader:
if row[0] == request.form['email']:
user_found = [row[0],row[1]]
if user_found[1] != request.form['pass']:
error = "wrong pass"
break
else:
flash("logged in")
session["user"]= user
return redirect(url_for('user'))
else:
error = "user not found"
else:
if "user" in session:
return redirect(url_for("user"))
return render_template('login.html',error=error)
a better way to do this is to use flask-login. Views that require your users to be logged in can be decorated with the login_required decorator:
after installing flask-login, put the following in your main file or init.py
...
app = Flask(__name__) # constructs the Flask app
app.config.from_object('app.config.Config') # injects the configuration
db = SQLAlchemy (app) # flask-sqlalchemy # connects to SQLite DB
lm = LoginManager( ) # flask-loginmanager
lm.init_app(app) # init the login manager
then create your user model and inherit from user_mixin that provides default implementations for the methods that Flask-Login expects
class User(db.model, user_mixin):
then define the user loader
#login_manager.user_loader
def load_user(user_id):
return User.get(user_id)
in your login route, after checking the username and password (enc) add the following to login the user:
login_user(user)
now you can decorate your protected views with login_required
#app.route("/page2")
#login_required
def page2():
return render_template("page2.html")
#app.route("/page3")
#login_required
def page3():
return render_template("page3.html")
You can implement a wrapper function to check if a user is logged in. In that function utilize the global g variable.
from flask import g
from functools import wraps
def login_required(f):
#wraps(f)
def decorated_function(*args, **kwargs):
if not "user" in session:
return redirect(url_for('login'))
g.user = session["user"]
return f(**args, **kwargs)
return login_required
#app.route('/user')
#login_required
def user():
return render_template("page1.html")
For a full example take a look at the View Decorators portion of the Flask documentation

Check Flask cookie for user_id and obtain User if it exists or redirect if not

What I'm after is something like what the #login_required decorators accomplish but I'm not sure if a custom decorator would allow me to pass the User option back to my route function. There are several pages in my app that require the user to be logged in to access them so I am looking for the most efficient way/least code to copy into each access-restricted route that will verify they have a user_id in their cookie (logged in), cache get/query for their User object using the user_id, and carry on with the route function, else redirect to the login page if user_id is not present.
What I was hoping to do was something like:
#noteBP.route('/note', methods=['GET', 'POST'])
def new_note():
user_details = return_user_details_if_logged_in_else_redirect_to_login_url(next=request.url)
...
And that function would check for the user_id in the session cookie and send back the User object or redirect to the login page:
def return_user_details_if_logged_in_else_redirect_to_login_url(next=None):
user_id = session.get('user_id')
if user_id:
user_details = user_util.get_user_details_by_id(user_id)
return user_details
else:
return redirect(url_for('usersBP.login', next=next))
Turns out, redirect does not work the same way as Abort where it gets called even if you are inside another function so now I have do do additional processing back in the route function to check:
user_details = return_user_details_if_logged_in_else_redirect_to_login_url(next=request.url)
if not user_details:
return redirect(redirect_url)
I'm looking to avoid having to paste this chunk of code at the top of every access-restricted route. Is there a more efficient way/DRY approach to do this? if with a decorator, how do I get the user_details into the route function?
If you want to redirect in a function called inside a view, raise a RequestRedirect. If you want to redirect in a decorator, check if the user is not logged in and return a redirect rather than the actual view (or use the previous function to raise the redirect).
import functools
from flask import url_for, redirect, g
from werkzeug.routing import RequestRedirect
def require_login():
if g.user is None:
raise RequestRedirect(url_for('login'))
def login_required(view):
#functools.wraps(view)
def wrapped_view(**kwargs):
require_login()
# or
# if g.user is None:
# return redirect(url_for('login'))
return view(**kwargs)
return wrapped_view
#app.route('/secret2')
#login_required
def secret1():
return 'secret 1'
#app.route('/secret2')
def secret2():
require_login()
return 'secret 2'
Populate g.user in a before_request handler.
from flask import session
#app.before_request
def load_user():
g.user = None
if 'user_id' in session:
# use whatever caching logic you want here.
g.user = User.query.get(session['user_id'])
Populate session['user_id'] in your login view.
#app.route('/login')
def login():
if request.method == 'POST':
user = User.query.filter_by(username=request.form['username']).first()
if user and user.check_password(request.form['password']:
session['user_id'] = user.id
return redirect(url_for('index'))
return render_template('login.html')
Now you can access g.user from any route, without passing it explicitly. If you do want to pass it explicitly, modify login_required.
def require_login():
if g.user is None:
raise RequestRedirect(url_for('login'))
return g.user
def login_required(view):
#functools.wraps
def wrapped_view(**kwargs):
user = require_login()
return view(user, **kwargs)
return wrapped_view
#app.route('/secret')
def secret(user):
return 'user {} is logged in'.format(user.id)
Give that all of this except passing the user is part of Flask-Login, you should really reconsider using Flask-Login instead of trying to maintain your own solution.
A decorator is a function that wraps and replaces another function. Since the original function is replaced, you need to remember to copy the original function’s information to the new function. Use functools.wraps() to handle this for you.
This example assumes that the login page is called 'login' and that the current user is stored in g.user and is None if there is no-one logged in.
from functools import wraps
from flask import g, request, redirect, url_for
def login_required(f):
#wraps(f)
def decorated_function(*args, **kwargs):
if g.user is None:
return redirect(url_for('login', next=request.url))
return f(*args, **kwargs)
return decorated_function
To use the decorator, apply it as innermost decorator to a view function. When applying further decorators, always remember that the route() decorator is the outermost.
#app.route('/secret_page')
#login_required
def secret_page():
pass
Note:
The next value will exist in request.args after a GET request for the login page. You’ll have to pass it along when sending the POST request from the login form. You can do this with a hidden input tag, then retrieve it from request.form when logging the user in.
<input type="hidden" value="{{ request.args.get('next', '') }}"/>

flask-login user is set to anonymous after login

im new to flask and flask-login and ive been struggling with this for days.
Im trying to log a user in like this:
from creds import auth_username, auth_password, pgsql_dbuser, pgsql_dbpassword, pgsql_db1name
from flask import Flask, render_template, request, Response, redirect, url_for
from flask.ext.bcrypt import Bcrypt
from flask.ext.login import LoginManager, login_required, login_user, current_user, logout_user
import logging
import psycopg2
import uuid
import datetime
app = Flask(__name__)
app.secret_key = str(uuid.uuid4()) # <- required by login_manager.init_app(app)
bcrypt = Bcrypt(app)
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'index'
#app.route('/', methods=['GET','POST'])
def index():
page_name = '/'
if request.method == 'POST':
email = request.form['email']
candidate_password = request.form['password']
user = finduserindbbyemail(email)
if user != None:
password_hash = checkuserpasswordindb(email)
if bcrypt.check_password_hash(password_hash, candidate_password):
user_object = User(user)
result = login_user(user_object) # <- here for successful login
return redirect(url_for('loggedin', user_object=type(user_object), user=user, result=result, current_user=current_user))
else:
user_object = User(user)
error_message = "The password you entered is incorrect"
return render_template('index.html', error_message=error_message)
else:
error_message = "The email address you entered does not match any we have in our records"
return render_template('index.html', error_message=error_message)
if request.method == 'GET':
return render_template('index.html')
I have a User class and a user callback:
class User():
def __init__(self, user):
self.user = user
def is_authenticated(self):
return True
def is_active(self):
return True
def is_anonymous(self):
return False
def get_id(self):
return unicode(self.user)
#login_manager.user_loader
def load_user(user):
con = psycopg2.connect(database=pgsql_db1name, user=pgsql_dbuser, password=pgsql_dbpassword, host='localhost')
uuid = "'"+user+"'"
cur = con.cursor()
cur.execute("SELECT uuid FROM users WHERE uuid = "+ uuid)
uuid = cur.fetchone()
con.close()
if uuid != None:
user = unicode(uuid[0])
return User.get_id(user)
else:
return None
After authentication is successful (apparently?), the user is redirected to a loggedin page which has a #login_required decorator. But instead of loading the loggedin page, the app redirects the user to the login page, telling me the user isnt being logged in?
If try to send values to the page and i remove the #login_required decorator so i can see the page, this is what i see in the browser after 'logging in':
current_user.is_authenticated() = False
current_user.is_active() = False
current_user.is_anonymous() = True
current_user.get_id() = None
user_object = <type 'instance'>
user = 2ca1296c-374d-43b4-bb7b-94b8c8fe7e44
login_user = True
current_user = <flask_login.AnonymousUserMixin object at 0x7f2aec80f190> Logout
It looks like my user hasn't been logged and is being treated as anonymous?
Can anyone see what I've done wrong? I'm having a lot of trouble understanding how this is supposed to work.
Another reason you might not be able to log a user in or current_user is Anonymous after going through your login form: The active=false flag is set on the user in the db. This behavior is confirmed in the docs:
flask_login.login_user(user, remember=False, duration=None, force=False, fresh=True)[source]
Logs a user in. You should pass the actual user object to this. If the user’s is_active property is False, they will not be logged in unless force is True.
This will return True if the log in attempt succeeds, and False if it fails (i.e. because the user is inactive).
So, when you call login_user, you can do this:
login_user(user, remember=form.remember_me.data, force=True), if you want to allow inactive users to log in.
So.. I managed to get it to work, but not using the user_loader callback. For whatever reason, my user loader exhibits the same behaviour as this:
Flask-login with static user always yielding 401- Unauthorized
Anyway, I used a request_loader callback instead based on this example:
http://gouthamanbalaraman.com/blog/minimal-flask-login-example.html
so for a user logging in, which starts here:
if bcrypt.check_password_hash(password_hash, candidate_password):
user_object = User(user, password_hash)
result = login_user(user_object) # <- here for successful login
token = user_object.get_auth_token(user, password_hash)
return redirect(url_for('loggedin', token=token))
I create a user object which has the user's id and their password hash.
then i log the user in. then i create a time-serialized token of the user id and password hash using itsdangerous. the get_auth_token function is part of the User class. it looks like this:
class User():
def __init__(self, user, password_hash):
self.user = user
self.password = password_hash
.
.
.
def get_auth_token(self, user, password):
data = [str(self.user), self.password]
return serializer.dumps(data, salt=serializer_secret)
you need to create a serializer at the beginning of your code somewhere:
serializer = URLSafeTimedSerializer(serializer_secret)
So after the token is created, pass it to the loggedin view as a URL query parameter.
When you try to load a login_required page, like my loggedin page, which is where login_user redirects me to after a successful login, the request_loader callback is executed. it looks like this:
#login_manager.request_loader
def load_user_from_request(request):
if request.args.get('token'):
token = request.args.get('token')
max_age = 1
try:
data = serializer.loads(token, salt=serializer_secret, max_age=max_age)
username = data[0]
password_hash = data[1]
found_user = finduserindbbyuuid(username)
found_password = checkuserpasswordindbbyuuid(username)
if found_user and found_password == password_hash:
user_object = User(found_user, password_hash)
if (user_object.password == password_hash):
return user_object
else:
return None
else:
return None
except BadSignature, e:
pass
else:
return None
This is the point where my user_loader was failing. I was logging in successfully, but the user_loader was always returning None and so my user would be deemed as anonymous.
So with the request loader, it checks that the request URL contains a 'token' argument in the query string. if so, it takes its value and using itsdangerous, deserializes the data.
you can make the token expire with timed serializers, but there are also non timed ones. after the token is deserialized, take the user and password hash and check in the database if they exist, in exactly the same way that the user_loader was supposed to work.. i imagine? my user_loader didnt work so i was most probably doing it wrong.
anyway if a user and password match in the db, then return the user object and bam, login works.
Im not sure if im doing it the right way, cos pretty much flying by the seat of my pants. i saw examples where people used the token_loader, rather than the request_loader callback function, to load the token, but i couldnt figure out how to set & get the auth token to & from the client. maybe ill figure it out... one day...
if you have the same problem, maybe this might help? or just let me know what you think
cheers
I found this page when searching for help with Flask-Login + Flask-Dance. I was seeing current_user as AnonymousUserMixin in a handler with the #login_required decorator. In my case making sure #app.route is on the line above #login_required fixed the problem. The correct order is in the docs: https://flask-login.readthedocs.io/en/latest/#flask_login.login_required.

python flask do something at each route

I have a flask app where I have to delete and update user information, like this (simplified):
#app.route('/<user>')
def show_user(user):
""" Show user information """
render_template('show_user.html')
#app.route('/delete/<user>')
def delete_user(user):
""" Delete user from database """
delete_user_from_db(user)
return redirect(url_for('show_users', user=user)
#app.route('/update/<user>', method=["POST"])
def update_user(user):
""" Update user information """
update(user, stuff_from_POST)
return redirect(url_for('show_users', user=user)
For each of these methods I need to verify whether the user specified in the URL is really a valid user, so I'd do something like this at the beginning of all those functions:
if user not in mydb:
do(something)
abort(404)
This is rather cumbersome, and since I will be having more functions that depend on the user to be valid, I was wondering if it were possible to wrap that block in another function that gets automatically executed when those routes are called.
Thanks in advance.
Use the before_request hook function see docs
#app.before_request
def before_request():
if user not in mydb:
do(something)
abort(404)
Edit:
I tried this
from flask import Flask, request
app = Flask(__name__)
db = ['paul', 'steve', 'anna']
#app.before_request
def before_request():
if request.endpoint in ['show_user', 'delete_user', 'update_user']:
user = request.path[request.path.rfind('/') + 1:]
if user not in db:
return 'user not found', 404
#app.route('/<user>')
def show_user(user):
""" Show user information """
return 'hello %s' % user
#app.route('/other')
def show_other():
""" Demonstrates other routes are not effected by before_request """
return 'other stuff'
if __name__ == "__main__":
app.run()
It's not actually as neat as I hoped but it works.
It's a little unfortunate to have to maintain which endpoints do what in the before_request function. If you were doing REST you might be able to merge these endpoints into one and simply use different http methods for each action.

Check if Flask-HTTPAuth is authenticated inside view

I'm using Flask-HTTPAuth for authentication. I want to display different data from a view depending on if the request was authenticated or not. Decorating the view with auth.login_required only shows it to authenticated users. How can I test if the request was authenticated with Flask-HTTPAuth?
auth = HTTPBasicAuth()
#app.route("/clothesInfo")
#auth.login_required
def show_info():
return jsonify(blah blah blah)
What you want is actually very easy to implement. In your verify_password callback, you will get username and password set to '' when the user does not provide credentials. You can still return True from that function, and that will allow the anonymous user to access the endpoint.
The following example demonstrates this technique:
auth = HTTPBasicAuth()
#auth.verify_password
def verify_password(username, password):
if username == '' or password == '':
# anonymous user, we still let them in
g.current_user = None
return True
g.current_user = my_verify_function(username, password)
return g.current_user is not None
#app.route("/clothesInfo")
#auth.login_required
def show_info():
if g.current_user:
# prepare data for authenticated users here
pass
else:
# prepare data for anonymous users here
pass
return jsonify(data)
You can decorate a function that returns None with login_required. Calling it while not authenticated will return an error response, calling it while authenticated will return None.
# a dummy callable to execute the login_required logic
login_required_dummy_view = auth.login_required(lambda: None)
def is_authenticated():
try:
# default implementation returns a string error
return login_required_dummy_view() is None
except HTTPException:
# in case auth_error_callback raises a real error
return False
#app.route('/info')
def info():
if is_authenticated():
# logged in view
else:
# basic view
See also Default login_required rather than adding decorator everywhere.
Things are simpler since April 2020.
Flask-HTTPAuth 4.0.0 added optional argument to login required to do exactly this.
From the docs:
An optional optional argument can be set to True to allow the route to execute also when authentication is not included with the request, in which case auth.current_user() will be set to None. Example:
#app.route('/private')
#auth.login_required(optional=True)
def private_page():
user = auth.current_user()
return "Hello {}!".format(user.name if user is not None else 'anonymous')

Categories

Resources