Password-based authentication is no longer enough. Cybercriminals can get people’s passwords in multiple ways: phishing, brute-force and keyloggers are just a few examples. To deal with this problem, Two-Factor Authentication (2FA) allows you to prevent malicious actors from gaining access without your approval.
In this tutorial, I will show you what Two-Factor Authentication is, how it works and how you can use it within your Flask web application. In particular, I will focus on the TOTP algorithm for the generation of security codes.
Note: This tutorial is not for complete beginners. I assume that you have a basic understanding of Flask, as well as HTML and CSS.
What is Two-factor Authentication
When you log in to your email account, you provide your email address and your password. If everything is correct, you have access to your account. This authentication method is called Single-factor authentication, or 1FA. The only information (or factor) you need is a password.
Two-factor authentication, or generally speaking Multi-factor authentication (MFA), provides additional layers of security: more factors needed to log in means making the job of cybercriminals way harder. In general, Multi-factor authentication mechanisms work on three kinds of factors:
Knowledge factors are what we are mostly used to when we think about authentication. In fact, they deal with something that only the legitimate user knows, like a PIN or a password.
Possession factors are usually the second factor used in 2FA and they leverage something that only the legitimate user has, like a smartphone, a smartcard or a security token.
Lastly, inherence factors are related to something that only the legitimate user is. These are advanced authentication factors that need advanced technologies to work, like facial recognition, fingerprint or retina recognition.
In this tutorial, we are going to implement 2FA by using our smartphone as the second authentication factor. In particular, we will use an application like Google Authenticator or Microsoft Authenticator to generate temporary security codes, also called One-Time Password codes (OTP).
How Security Codes are Generated
Photo by Yura Fresh on Unsplash
Before starting the coding part, I believe it is necessary to understand how security codes are generated by authenticator apps, at least from a high-level point of view.
In this tutorial, we will deal with Time-Based One-Time Password (TOTP) generation. As the name suggests, TOTP uses the current time combined with a shared secret to generate passwords. The shared secret is usually a string of random bytes generated by the server, which is shared between the server and the user only. This shared secret allows the user to generate One-Time passwords and the server to verify that they are correct. Typically, One-Time passwords are converted to 6-digit numbers for ease of use and they expire after 30 seconds.
Now, without further discussions, let’s see how we can implement this extra layer of security into our Flask web application.
Step 1. Installing requirements
Note: We will first create a web application with a simple login form and then, we will implement 2FA. For the sake of this tutorial, a database is not needed. Of course in real-world applications, passwords are not stored in the source code or the database, but they are first hashed and then stored in the database.
Apart from the Authenticator app, there are a few other things we need. I have prepared a GitHub repository if you want to download and replicate the project. The repository has two branches: the main one, and 2FA, which implements Two-factor authentication. You can start with the main branch and follow me through the implementation of two-factor authentication.
After you download and unzip the archive, cd into the directory. You can install all the requirements by typing:
$ pip install -r requirements.txt
As you may notice, we will only use Flask and PyOTP, which is a Python library for generating and verifying one-time passwords.
Step 2. Creating a basic web application with 1FA
First, we have to create the following directories according to this structure, where the base directory is Flask1FA:
Flask1FA |___static |___css |___templates
After creating all the files, the directory will look like this:
Flask1FA |___static |___css |___normalize.css |___skeleton.css |___style.css |___templates |___index.html |___layout.html |___success.html |___requirements.txt |___server.py
Since you are familiar with Flask, you know that it uses Jinja2 as its template engine. So,
layout.html contains the default structure of every webpage of this web application. This layout will allow us to create
index.html with less than 30 lines:
Nothing special here, a really simple layout where everything lies inside a single container. To make this tiny application more stylish, I used Skeleton. Skeleton is a Responsive CSS Boilerplate and it is perfect when you are creating small projects and you don’t want to use Bootstrap. Lightweight and easy to use, go check it out if you want 😃
As I was mentioning before, by extending layout.html, we easily get the homepage:
To personalize the views a little bit, we also add some CSS:
And this is how it looks like:
Image showing the Login page - Photo by Author
Lastly, after the HTML part, we have to deal with the logic. In particular, our Flask web application will have two routes:
The first route will render our homepage, while the second route handles the authentication step. When you click the Login button, it will do a POST request to
/login and this will trigger the credential verification process. As you already know Flask a bit, the source code is really simple and you can copy and paste it into
login() function handles the authentication process. In this particular case, I have stored the credentials inside the Flask’s config for simplicity. The credentials checking is done inline 15. If the username or the password provided don’t match with the ones defined in lines 23 and 24, the user will get feedback thanks to Flask flashing-system. Note that on line 25 we randomly define an attribute called
SECRET_KEY. This attribute will be used by Flask for multiple security reasons, one of which is signing the session cookie.
Step 3. Adding Two-Factor authentication
To implement 2FA we need to:
server.py, because we have to add the logic for the second step
- Create another view to display the second step, which we will call
Modifying Flask code
The idea is that after the first step has been passed successfully, we have to ask the user to insert his One-Time password. Since we are not dealing with databases and user preferences, we are going to assume that if it is the first time that we see the user, we ask him to configure 2FA . The next time, we will ask for the code. To show the process, here’s a diagram:
Image showing the Login process idea- Photo by Author
So, we modify the
/login route in this way:
We have only added two lines: lines 7 and 8. Once we know that the credentials are correct, we store the username in the current session because we need it later. Then, we redirect the user to the next section.
The most important part is the next one:
In this section, we are using Pyotp. This library will help us deal with the process of password generation and verification. On line 7 we initialize the TOTP algorithm with the shared secret and then we check if the code provided by the user is the same generated by the server.
There are two new attributes in the configuration:
OTP_CODEis the randomly generated shared secret
OTP_ENABLEDis a boolean value that I use to keep track of whether the user has 2FA enabled or not.
Just to point it out, in real applications these pieces of information would have been stored somewhere else, in a different way.
To sum it up, this is our
server.py after the modifications:
We are almost done, we only need to create the last page. We can exploit the fact that Flask uses Jinja2 to make our work easier here. This section will need to display two different things, according to the situation. If the user has not enabled 2FA this page has to show how to do it and, most importantly, it needs to provide the randomly generated shared secret. Otherwise, if it is not the first time the user visits the page, the view will render a normal form.
Image showing the second-step verification page - Photo by Author
After you have configured your Authenticator app, you just need to click on Done. Then type in the code that the apps generate and the authentication is done!
This is the code for
I hope you enjoyed this simple project and please, feel free to point out if you have any questions or you think something is wrong. Cyber security is a serious matter and this is just a basic example that you don’t want to use in real-world applications. I think it is really cool though to get a sense of how these projects can be done and I also found it funny to do it myself.
Thank you for your time 😃
- Flask: https://flask.palletsprojects.com/en/2.0.x/
- Skeleton: http://getskeleton.com/
- Two-factor authentication, Reading 1: https://blog.malwarebytes.com/101/2018/09/two-factor-authentication-2fa-secure-seems/
- Two-factor authentication, Reading 2: https://www.nist.gov/blogs/cybersecurity-insights/back-basics-whats-multi-factor-authentication-and-why-should-i-care
- GitHub repo: https://github.com/lorenzobn/Flask1FA