In this article I'm going to introduce an authentication scheme known as two factor authentication. As the name implies, this method requires the user to provide two forms of identification: a regular password and a one-time token. This greatly increases account security, because a compromised password alone is not enough to gain access, an attacker also needs to have the token, which is different every time. You can see me do a short demonstration of this technique in the video above.

As usual, this article includes a complete example that implements this authentication technique in a Flask application. You may think this is going to be an advanced article that needs complex cryptographic techniques, specialized hardware and/or proprietary libraries, but in reality it requires none of the above. The solution is relatively simple to add if you already have username and password authentication in place, and can be done entirely with open standards and open-source software. There are even open-source token generation apps for your Android or iOS smartphone!

The Example Application

As mentioned above, in this article I present a complete example application. This application demonstrates how to do two factor authentication in a web application that uses Flask and Flask-Login. The source code for the example application is hosted in the following Github repository: https://github.com/miguelgrinberg/two-factor-auth-flask. See the README file for installation instructions.

Introduction to Two Factor Authentication

Letting users authenticate to an application just with a username and password combination is inherently risky, because when the password is compromised the attacker obtains full access. To reduce that risk, security conscious applications can implement multi-factor authentication, which requires the user to provide additional additional proofs of identity. With two factor authentication, the user must provide the password, plus a second authentication factor.

Some of these accessory identity verification schemes are based on a physical characteristic of the user, such as a fingerprint, or an iris scan. Another common way to prove identity is with a physical object that the user carries at all times, such as a card with a magnetic strip, or a portable token generator.

If you work for a company that lets you connect to the office from home through a VPN, chances are you are already familiar with two factor authentication, and you already have a token generation device or app similar to these:

For the example application in this article I'm going to concentrate on this type of authentication factor, which is based on one-time password generation algorithms.

One-Time Passwords

The idea behind one-time passwords is that they are only valid for a single login session. These passwords are generated algorithmically by a hardware device or a smartphone app. To validate a one-time password, the server runs the same algorithm and compares the result with the password provided by the user. The difference with a traditional password is that the user does not need to memorize anything, the generated password is displayed by the token generation device and the user just copies it to the login form.

There are many one-time password algorithms, most are proprietary, but there are a few open standards, of which HOTP and TOTP are the most commonly used.

The HOTP algorithm, short for HMAC-based One-time Password, is described in RFC 4226. This algorithm generates tokens based on a secret and a counter, both known by the token generation device and the authentication server. Each time a token is used the counter is incremented on both sides, and that makes the algorithm generate a different token for the next login attempt.

The TOTP algorithm, short for Time-based One-time Password, is described in RFC 6238. This standard also uses a shared secret, but deals away with the counter, which is replaced by the current time. With this algorithm the token changes at a predefined time interval, usually every 30 seconds.

The benefit of TOTP over HOTP is that tokens are a function of time, and thus are constantly changing. That means that even if an attacker can take a peek at the current token displayed on your smartphone app, a few seconds later it will be superseded by a new one. The disadvantage of TOTP is that it requires the token generator and the authentication server to have their clocks set to approximately the same time. This is not a problem for the smartphone, but on the server it is recommended that you run an NTP client to keep the clock from drifting.

For the example application that I present in this article I'm going to use TOTP, but it should be fairly easy to adapt the application to use HOTP.

The First Factor: Password Authentication

I'm not going to spend a lot of time describing how to do password authentication because I have written extensively about it. But I feel it is always good to repeat a few best-practices to keep user passwords safe:

Passwords must never be stored in the database. Store a password hash instead.

To verify a password, calculate its hash, then compare it against the hash stored in the database.

Always use secure HTTP to transmit forms that contain passwords.

If you would like to see a fairly complete implementation of password authentication that includes user registration, email verification and password resets, you will find one in my book. The source code featured in the book is in this Github repository.

For this example I used a much simpler setup, because I did not want to complicate the example with features that are largely unrelated to this article. I have basically skipped email verification and password reset options, but note that these are necessary for a production application, even when two-factor authentication is added to the mix.

For this application, I used Flask-SQLAlchemy, Flask-Login, Flask-WTF and Flask-Bootstrap, so for those that have read my other Flask tutorials this is going to be a fairly familiar application. The User model class is shown below:

from werkzeug.security import generate_password_hash, check_password_hash from flask.ext.login import UserMixin from app import db, lm class User(UserMixin, db.Model): __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(64), index=True) password_hash = db.Column(db.String(128)) @property def password(self): raise AttributeError('password is not a readable attribute') @password.setter def password(self, password): self.password_hash = generate_password_hash(password) def verify_password(self, password): return check_password_hash(self.password_hash, password)

As you can see, I'm hashing the passwords using Werkzeug's hashing functions. Note that the password property allows me to say user.password = 'some-password' , and this will automatically trigger the hash of the password to be stored in user.password_hash . The original password is then discarded.

The user registers an account by providing a username and a password. The password is asked twice to ensure it is typed correctly. Here is the Flask-WTF form that handles this:

class RegisterForm(FlaskForm): username = StringField('Username', validators=[Required(), Length(1, 64)]) password = PasswordField('Password', validators=[Required()]) password_again = PasswordField('Password again', validators=[Required(), EqualTo('password')]) submit = SubmitField('Register')

Once users are registered, they can login by entering the username and the password on the login form, which you can see below:

class LoginForm(FlaskForm): username = StringField('Username', validators=[Required(), Length(1, 64)]) password = PasswordField('Password', validators=[Required()]) submit = SubmitField('Login')

The route that handles the registration form is pretty straightforward, so I'm not going to show it here. It basically takes the username and the password submitted by the user and adds then as a new user to the database. Below you can see the route that logs a user in:

@app.route('/login', methods=['GET', 'POST']) def login(): if current_user.is_authenticated: # if user is logged in we get out of here return redirect(url_for('index')) form = LoginForm() if form.validate_on_submit(): user = User.query.filter_by(username=form.username.data).first() if user is None or not user.verify_password(form.password.data): flash('Invalid username or password.') return redirect(url_for('login')) # log user in login_user(user) flash('You are now logged in!') return redirect(url_for('index')) return render_template('login.html', form=form)

Here you can see how the password verification is done, simply by calling user.verify_password() in the model.

If you look at the commit history in the Github repository, you can find a commit that adds all the code related to password authentication together. You can also checkout that commit and try the application at that stage if you like. Once you feel you have a good understanding of the application at this stage, you are ready to move on to the tokens.

The Second Factor: TOTP Tokens

A search on pypi revealed a few packages that implement the TOTP algorithm. I tested a few of them and decided on onetimepass, a small library that supports HOTP and TOTP and is compatible with Python 2 and 3. If you want to learn how these tokens are calculated, I recommend that you read the source code, which is short and easy to understand.

The User Model

Now that the problem of calculating and verifying tokens is solved, let's look at the changes that add token support to the user model:

import os import base64 import onetimepass class User(UserMixin, db.Model): # ... otp_secret = db.Column(db.String(16)) def __init__(self, **kwargs): super(User, self).__init__(**kwargs) if self.otp_secret is None: # generate a random secret self.otp_secret = base64.b32encode(os.urandom(10)).decode('utf-8') # ... def get_totp_uri(self): return 'otpauth://totp/2FA-Demo:{0}?secret={1}&issuer=2FA-Demo' \ .format(self.username, self.otp_secret) def verify_totp(self, token): return onetimepass.valid_totp(token, self.otp_secret)

The user model gets an additional field called otp_secret that stores the shared secret that the TOTP algorithm uses as input. This should be a binary string of length 10 encoded as a base32 string, which makes it a printable string with 16 characters. The __init__() constructor sets it to a random string if a value for this field isn't given as an argument.

The get_totp_uri() function returns an authentication URI. This is used to transfer the shared secret and additional account information to the smartphone. This URI will be rendered as a QR code that you have to scan with your phone. Below you can see the structure of these URIs:

otpauth://<protocol>/<service-name>:<user-account>?secret=<shared-secret>&issuer=<service-name>

Here the <protocol> can be totp or hotp . The <service-name> is the name of the service or application that the user is authenticating to. The <user-account> can be the username, the user's email address or anything that identifies the user account. The <shared-secret> is the code that is used to seed the token generator algorithm. The issuer argument is normally set to the service name. A period optional argument can also be used to change the interval for token changes, which defaults to 30 seconds. For more information about these URIs, see the documentation on the Google Authenticator wiki.

Finally, the verify_totp() function takes a token as input, and validates using the support provided by the onetimepass package.

User Registration

There are several possible options to consider when implementing the user registration flow. For some applications it may make sense to leave user registration as is, and then give users the option to optionally enable two factor authentication if they wish so. For other applications, two factor may be mandatory, so it is incorporated into the registration process.

For this application, I have opted for the latter, so immediately after submitting the user registration page the user is presented with a two factor authentication setup page that looks like this:

Here the user needs to start the token generator app on the smartphone, and use it to scan the QR code. This is all it takes to register the shared secret and account information on the phone. After this step is done, the user can go to the login page and login using password and token for the first time.

The changes to implement the QR code page are not as scary as they may seem. First, the original registration route is changed to redirect to a new route that I called two_factor_setup instead of sending the user to the login page. Before redirecting, it adds the username to the user session, so that the QR code page knows what user is registering. Here are the changes to the registration route:

@app.route('/register', methods=['GET', 'POST']) def register(): # ... form = RegisterForm() if form.validate_on_submit(): # ... # redirect to the two-factor auth page, passing username in session session['username'] = user.username return redirect(url_for('two_factor_setup')) return render_template('register.html', form=form)

And below you can see the implementation of the two_factor_setup route:

@app.route('/twofactor') def two_factor_setup(): if 'username' not in session: return redirect(url_for('index')) user = User.query.filter_by(username=session['username']).first() if user is None: return redirect(url_for('index')) # since this page contains the sensitive qrcode, make sure the browser # does not cache it return render_template('two-factor-setup.html'), 200, { 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0'}

After validating that there is a username stored in the user session, this route ensures the user exists, and if it does it just renders a new template called two-factor-setup.html . This page is served with extra headers that tell the browser to not do any caching. The reason is that this page will include a QR code that can give an attacker access to the time based tokens, so it's best to take precautions and make sure there are no copies of the QR code lost in a cache.

The two-factor-setup.html template is also fairly simple:

{% extends "base.html" %} {% import "bootstrap/wtf.html" as wtf %} {% block page_content %} <h1>Two Factor Authentication Setup</h1> <p>You are almost done! Please start FreeOTP on your smartphone and scan the following QR Code with it:</p> <p><img id="qrcode" src="{{ url_for('qrcode') }}"></p> <p>I'm done, take me to the <a href="{{ url_for('login') }}">Login</a> page!</p> {% endblock %}

The page includes a reference to the QR code image, but the URL for this image is not a usual image link, it is a dynamic URL generated with Flask's url_for() function. This is because the QR code image has to be generated specifically for each user, so a Flask route is invoked to do this work.

This qrcode route is different than the rest, because instead of returning HTML it returns image data, in this case in SVG format. To generate the QR code I'm using package pyqrcode. Here is the code for this route:

@app.route('/qrcode') def qrcode(): if 'username' not in session: abort(404) user = User.query.filter_by(username=session['username']).first() if user is None: abort(404) # for added security, remove username from session del session['username'] # render qrcode for FreeTOTP url = pyqrcode.create(user.get_totp_uri()) stream = BytesIO() url.svg(stream, scale=5) return stream.getvalue(), 200, { 'Content-Type': 'image/svg+xml', 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0'}

Here once again I check that the username is in the session and that it is a known user. If any of those checks fail, I return a 404 error code, which to the browser will look like it requested an image file that does not exist. If the user is valid, I quickly remove it from the session, because once the user requests the QR code I want to make sure it this image cannot be requested again. This means that if the user does not scan the QR code in this only occasion it is presented, then the account is not going to be accessible.

The information stored in the QR code is the URL that includes the TOTP data. This is what most TOTP smartphone apps expect, something that the Google Authenticator app started and everyone else copied. Recall that I've added a method that generates this URL as User.get_totp_uri() above. The QR code rendering simply involves using the pyqrcode functions to render the TOTP URL as a SVG image, which I save to a StringIO stream in memory. This buffer is then returned as a response with the correct content type for SVG images. I also threw in the headers that disable caching for the image, as I did with the parent HTML page.

The user now can scan the QR code with a TOTP enabled smartphone app, and as soon as that is done the registration process is complete. Pretty cool, right?

Login

The only piece that is left to do is to extend the login form to accept a token, and also to validate it. I mentioned above that applications may opt to make two factor authentication optional or mandatory. If this is made optional, then the login form does not change, users enter their username and password, and upon verification the application can find out if two factor authentication is enabled and present an additional form where the user enters the token. In the case of this application, however, two factor setup is required for all accounts, so I decided to make a single login dialog that accepts username, password and token together.

Here is the improved login form, which just gets an extra field for the token.

class LoginForm(FlaskForm): username = StringField('Username', validators=[Required(), Length(1, 64)]) password = PasswordField('Password', validators=[Required()]) token = StringField('Token', validators=[Required(), Length(6, 6)]) submit = SubmitField('Login')

And then the login route is simply enhanced with an additional validation check:

@app.route('/login', methods=['GET', 'POST']) def login(): # ... form = LoginForm() if form.validate_on_submit(): user = User.query.filter_by(username=form.username.data).first() if user is None or not user.verify_password(form.password.data) or \ not user.verify_totp(form.token.data): flash('Invalid username, password or token.') return redirect(url_for('login')) # log user in # ... return render_template('login.html', form=form)

Here the only addition is the call to user.verify_totp() , the method that I added to the user model to check tokens using the onetimepass package. Note that when authentication fails I do not give any clues about what part has failed, the error message is pretty generic.

And that is all, the application with two factor authentication is now complete!

Possible Improvements

This application is focused on simplicity, I tried to not over complicate it so that the core concepts of working with tokens are easy to understand. However, in a real world application you may want to make things a bit more robust, so I'm going to discuss some of these additions that may be useful.

As I mentioned above, for many types of applications using two factor authentication should be optional, so it is only for users that want to take advantage of the stronger account security. For these applications, user registration and login pages do not need to change. Instead, a settings page allows users to enable and set up two factor authentication once they are logged in. The login process is then split in two parts, the first part is the regular username and password login, and then for those users that enabled two factor authentication a second page requests the token.

A common practice for applications that allow users to edit sensitive account settings is to request the user to authenticate again right before making account changes. For example, to change your password, applications typically ask you to enter your old password first. When two factor authentication is used, the application should ask the user to provide the current token as well, but it is important that this is a new token that was not used before. Imagine that somehow an attacker was able to crack your password, and also spied on your phone and knows the current code. That means that for up to 30 seconds you are vulnerable. If the attacker logs in to your account and then quickly goes to the account settings page entering the password and the same token again, your account is compromised. Now the attacker can disable two factor authentication and gain full access in the future. To avoid this, an application should save any tokens that were consumed to the user database and not allow the same token to be used a second time. The attacker will be forced to wait those 30 seconds before entering the settings page, so it gets even harder to have the account compromised.

An alternative to use a smartphone app to generate the tokens is to have the server send an email or SMS with the current code. The onetimepass package that I used for the example application not only provides a function to validate TOTP codes, it has a function that just returns the current code, so you can then send it to the user as you see appropriate. For this type of workflow it may be necessary to increase the period at which tokens change.

Account recovery is harder when two factor authentication is used. If an application allows users to regain access to their accounts without having a valid token, then an attacker can take advantage of this facility as well. Typically users that are locked out of their accounts have to contact an administrator and have their accounts reset manually. You can also opt to add another form of verification, such as security questions, but of course this in part undermines the increased account security.

As mentioned in the reddit discussion of this article, there are a couple of implementation details that can be improved to make the application more secure. Storing the OTP secret and the hashed passwords in the same table can be seen as a security risk, because in the event of a security breach that gives the attacker access to the database, both will be accessible. To mitigate this risk, you could choose to store these two sensitive items in different database tables, or even better, different databases altogether. Encrypting the OTP secret, maybe using Flask's SECRET_KEY as encryption key, can also help. In all cases secure HTTP must be used for all communications that include passwords and the OTP secret (which is encoded in the QR code).

Conclusion

I hope this was a useful article. I would love to hear what you build with the techniques I presented in this article, so please let me know of any projects you make. As always, feel free to write your questions below.

Miguel