In this post we’re going to add session-based authentication to our Flask application. Session-based authentication is super simple and very flexible, and it's perfect for Flask applications that use render_template
. Those applications are best suited for the type of user login we'll be adding in this post.
Prerequisites
In case you don't have a Flask app handy, here's some starter code you can use. It's incomplete, because we'll be completing it in this post!
Link: https://github.com/tecladocode/simple-flask-template-app
The Flask application provided has three endpoints, home
, login
, and register
. The login
and register
endpoints contain forms. We will use those forms to receive data from the user.
There's also an app.secret_key
defined, which is used to cryptographically sign the cookies (learn more in a second!).
How authentication works
First request
Flask sends a cookie to a user on every request. This happens automatically. The user’s browser stores the cookie that Flask sends together with the domain of the website that sent it. Then, the browser will send the cookie back every time the user accesses that domain.
For example, let’s say the user first comes to our website by accessing the domain http://127.0.0.1:5000/hello. Since it is the first time the user is accessing our site, they do not have a cookie in their browser related to our site, so they cannot send us it.
Flask generates a new one and stores inside it a unique identifying string of characters for this new browser. Remember a browser might be a user, but also multiple people could be using the same browser. This will be relevant later.
When our Flask application responds, it will automatically attach this new cookie. The user's browser then stores it (as long as the user has cookies enabled!). In future requests, the user’s browser will include this cookie together with other request data.
Now let’s see what happens on any subsequent request.
Subsequent requests
Let's say now the user makes a request to http://127.0.0.1:5000/another. This time, the user’s browser has a cookie related to our domain, so it includes it in the request by default.
When Flask receives the cookie in the request, it extracts the information which uniquely identifies the browser making the request. Now Flask knows something about the browser that made the request. But what does it know? Well, that depends on what we want to store!
This mechanism of cookies allows Flask to identify browsers, and for each browser it can store any arbitrary information. For example, if the user sends us their name, we could store their name against their browser, so that we remember it in subsequent requests if they send us their cookie.
We use the secret_key
to sign the cookies. That's how our Flask app can tell that it created the cookie, and not a different app. The secret_key
should be long, secure, and secret. Otherwise it's easy for someone to create a fake cookie and feed it to our app. They could then trick us into thinking they are logged in as another user, and steal their data!
Storing data against a browser is called "creating a session". So, a cookie is the data the browser holds, and a session is the data the server holds. They are related, but also separated.
User authentication
Through this mechanism, we will implement user logins. Since for each browser we can store some information, we can use that to store whether a browser is "logged in" or not.
Here's the flow:
- User sends us a valid username and password.
- We verify that they are valid.
- If they are, we store the user’s username against their browser in a session.
- When this browser sends us requests, we can treat it as a "logged in" browser. We this because they included the appropriate cookie, and when we get related session we can see there is a username in it. Remember there can only be a username there if they logged in successfully earlier.
- If the user ever wants to log out, we just remove the data from their session. In future requests we will not see a username there, so we will treat the browser as "logged out".
To recap: if the session contains a username, we treat that request as a logged in request. If it does not, we treat it as a logged out request.
Implementing login
Let’s see how to implement a Flask endpoint to authenticate (log in) a user. We must receive data from the user via the form in the template. This will be done via a POST
request since that is the form's method in the provided sample code.
@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
username = request.form.get("username")
password = request.form.get("password")
if username in users and users[username][1] == password:
session["username"] = username
return redirect(url_for("home"))
return render_template("login.jinja2")
What we've done here is first of all, allow this endpoint to receive both GET
and POST
requests.
Then, if we're handling a POST
request we know we're dealing with form data. We'll try to get the username and password from the form data.
If the username is one of our users, and their password is correct, then we'll store their username in the session, under session["username"]
.
We then redirect them to the homepage, where we'll display the user's name if it is in the session (or "Unknown"
if it is not):
@app.route("/")
def home():
return render_template("home.html", name=session.get("username", "Unknown"))
And in any other endpoint, you can check whether the user is logged in or not, potentially redirecting them to another endpoint where they can log in.
A normal flow would be to check whether the session
contains a username. If it does, then the user is logged and you can allow your endpoint to do its thing. If it does not, then you can redirect them to the login endpoint (in the same way as shown above).
Implementing register
Registering users is very simple. With the username and password data they give us, we just check that the username doesn't already exist. Then we add it to the dictionary:
@app.route("/register", methods=["GET", "POST"])
def register():
if request.method == "POST":
username = request.form.get("username")
password = request.form.get("password")
if username not in users:
users[username] = (username, password)
return render_template("register.html")
Note that both the login
and register
endpoints are missing some crucial error handling. It's always good to tell users when their username or password is incorrect, as well as when they've tried to register a username that already exists. At the moment our code does neither.
Implementing logout
To log a user out, we just need to clear the session—removing the username from it means that subsequent requests will be treated as "logged out". Do note that the browser will still have the cookie, and Flask will still have a session. It just will be empty.
@app.route("/logout")
def logout():
session.clear()
return redirect(url_for("home"))
Final code
Have a look at the final, completed code for our simple application with user authentication.
We've implemented login, logout, and user signup.
Link: https://github.com/tecladocode/simple-flask-template-app/tree/feature/login
On Flask apps that don't render templates
This method of authentication only works on apps that both serve the content and handle the user’s data. It won’t work on web apps developed with JavaScript. That’s because this session-based authentication requires that the Python code can read the data stored against the user in every request or page change.
For web apps that use JavaScript, you may want to use token based authentication. We talk about that in this blog post.
Want to learn even more about session based authentication, including how to have different types of users and access levels? Check out this blog post!
Authentication extensions for Flask
There are some extensions for Flask which can do a lot of this work for you. However it is important to understand how this all works behind the scenes before using them. Now that you know how it all works, you can consider using an extension.
Flask-Login is one such extension, which can do the following for you:
- Store the active user’s ID in the session, and let you log them in and out easily.
- Let you restrict views to logged-in (or logged-out) users.
- Handle the normally-tricky “remember me” functionality.
- Help protect your users’ sessions from being stolen by cookie thieves.
I recommend reading the official documentation if you want to learn more.
Wrapping up
If you're learning Python and you find this kind of content interesting, be sure to follow us on Twitter or sign up to our mailing list to stay up to date with all out content. There's a form at the bottom of the page if you're interested.
We also have a Complete Python Web Course for much more in depth training and tutorials, as well as walkthroughs for multiple projects. We have a 30 day money back guarantee, so you really have nothing to lose by giving it a try. We'd love to have you!
Also, sign up for our mailing list below. We'll be sending discount codes to our subscribers every month!