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'))
The cookie used to store session data is known session cookie. However, unlike an ordinary cookie, Flask Cryptographically signs the session cookie. It means that anyone can view the contents of the cookie, but can't modify the cookie unless he has the secret key used to sign the cookie.
The default session is implemented using secure cookies. Cookies are persisted by the client's browser, Flask doesn't do anything in that regard. Each client has a unique session cookie, which it sends to the Flask server with each request.
By default, Flask-Login uses sessions for authentication. This means you must set the secret key on your application, otherwise Flask will give you an error message telling you to do so. See the Flask documentation on sessions to see how to set a secret key.
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'))
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)
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'])
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With