Python Flask: keeping track of user sessions? How to get Session Cookie ID?
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 wrapsfrom flask import flashdef 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_requireddef 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_requireddef 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 secretapp.secret_key = 'something simple for now' from flask import flash, sessiondef 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'])