Two Factor Authentication with Flask

Posted by
on under

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

Become a Patron!

Hello, and thank you for visiting my blog! If you enjoyed this article, please consider supporting my work on this blog on Patreon!

57 comments
  • #26 Hellabarger said

    @burrito: I get what you are saying. If you have the username and password you can get the qrcode. This is just an example. In the real world setting the qrcode or temp code/token would be sent to the users matching phone or email. Great example, thanks Miguel

  • #27 Miguel Grinberg said

    @Hellabarger: How do you get the qrcode if you have the username and password? You guys clearly see something I'm not seeing, so if you found a vulnerability I want to know what it is.

  • #28 Sean Clark said

    Hi Miguel
    This is a great article and I have used it and the code used in your book to successfully enhance our web applications. Please could you suggest how to extend the api authentication to include 2 step authentication.

  • #29 Miguel Grinberg said

    @Sean: two factor authentication is for users, so when you ask about doing it for an API I'm not sure I understand exactly what you mean. If you have an API route that takes the user credentials and returns a token, you can extend it to also take a code for the second factor, and then the token would be returned only if the credentials and the 2nd factor code are both valid. Is that what you were looking for?

  • #30 Umair said

    Great example. I'm building an app and I need to do theexact same thing that you've done but my requirement is that I have to do all of this using Mongo DB. is there any sample that uses mongo db to register, login and 2 factor authentication?

  • #31 Miguel Grinberg said

    @Umair: which database you use has nothing to do with the topic of this article. Just find any article on managing users with mongo and then add the two factor auth on top. Unfortunately I haven't written MongoDB tutorials, you'll have to look elsewhere.

  • #32 Terence said

    Im currently using this in conjunction with your flask mega tutorial. I keep getting the error at
    url = pyqurcode.create(user.get_totp_uri())
    AttributeError: 'function' object has no attribute 'get_totp_uri'

    The User class is defined in models.py which contains the get_totp_uri() outlined in this tutorial. This User class is imported into routes.py which contains the def qrcode() procedure containing the line generating the above error.

    I've tried a number of things to make this work, including attempting calls directly on the User class and utilizing current_user, but I can't make it work for me. Any ideas on where I can look to get around this issue?

  • #33 Miguel Grinberg said

    @Terence: hard to know without seeing your code, but the error seems to suggest "user" is a function. Maybe you are using the name "user" for multiple things?

  • #34 Terence said

    Is it not the "user" defined by the line earlier in qrcode()?

    user = User.query.filter_by(username=session['username']).first()

    This is what has me confused, I'm not sure how this would ever have an attribute get_totp_uri(). I must be missing something more fundamental in my understanding.

  • #35 Miguel Grinberg said

    @Terence: The get_totp_uri() method is defined by myself. It's shown here in this article when I show how the User model is defined.

  • #36 Mike said

    Hello Miguel,
    I tried to run your code but when i run

    if name == 'main':
    app.run(host='0.0.0.0', debug=True)

    I get this message:

    if name == 'main':
    app.run(host='0.0.0.0', debug=True)
    * Restarting with stat
    An exception has occurred, use %tb to see the full traceback.

    SystemExit: 1

    What should i do?

    Thanks for your useful guide, i will buy asap your Flask Guide. Hope you'll answer me soon

  • #37 Miguel Grinberg said

    @Mike: I'm pretty sure you are not running "python app.py" to start the application. Try to run it this way.

  • #38 Selena said

    I register and use Google Authenticator to scan qrcode successfully. But when login with token that provided by Google Authenticator the page at http://two-factor-auth.herokuapp.com/login said Invalid username, password or token.
    Pleaaaaaase heelp!!!

  • #39 Miguel Grinberg said

    @Selena: you need to add debugging to find out if it is the password or the token that are incorrect. I can't really tell you what's wrong without the additional debugging.

  • #40 Ryan Hutchins said

    Hi Miguel,

    I discovered your blog today. I am an experienced programmer, but I'm new to Flask. I really appreciate you making all of these tutorials publicly available. I am also interested in creating a blog for my hobbies. What do you use as a hosting solution, and how much does it cost you per months?

    Thanks!

    Ryan

  • #41 Miguel Grinberg said

    @Ryan: this blog is hosted on a Digital Ocean droplet: https://www.digitalocean.com/products/droplets/

  • #42 Mark Angelo Unida said

    Hi do you have implementation of this through mysql db?

  • #43 Miguel Grinberg said

    @Mark: No, but nothing that relates to 2FA will change, anyway.

  • #44 Daniel Hernandez said

    Hi, after follow your great explanation, the QR code is invalid for APP, it is happening because self.otp_secret is not populate column. Which is the trigger for it?

  • #45 Miguel Grinberg said

    @Daniel: Did you define a value for this column? If you added the column to an existing database, then all the entries that are currently in the database will have the column defined as NULL value. You have to add the otp_secret for these entries before you can use them in 2FA.

  • #46 Daniel said

    Yes it was defined, it was in user created before configuration, for new user otp_secret is populated. Thanks @Miguel

  • #47 Carlyle Adams said

    Thank you for putting this up.
    I'm having some problems just running it. :(
    I'm new to everything and just want to learn.
    Can you please help?
    Is it because I'm on python 3.10

    [[source]]
    url = "https://pypi.org/simple"
    verify_ssl = true
    name = "pypi"

    [packages]
    click = "==6.7"
    dominate = "==2.3.1"
    itsdangerous = "==0.24"
    onetimepass = "==1.0.1"
    six = "==1.11.0"
    visitor = "==0.1.3"
    Flask = "==0.12.2"
    Flask-Bootstrap = "==3.3.7.1"
    Flask-Login = "==0.4.0"
    Flask-SQLAlchemy = "==2.3.0"
    Flask-WTF = "==0.14.2"
    Jinja2 = "==2.9.6"
    MarkupSafe = "==1.1.1"
    PyQRCode = "==1.2.1"
    SQLAlchemy = "==1.1.14"
    Werkzeug = "==0.12.2"
    WTForms = "==2.1"

    [dev-packages]

    [requires]
    python_version = "3.10"

    (two-factor-auth-flask-master) PS C:\Users\carly\two-factor-auth-flask-master> python app.py
    Traceback (most recent call last):
    File "C:\Users\carly\two-factor-auth-flask-master\app.py", line 4, in <module>
    from flask import Flask, render_template, redirect, url_for, flash, session, \
    File "C:\Users\carly.virtualenvs\two-factor-auth-flask-master-syG0u3oG\lib\site-packages\flask__init__.py", line 17, in <module>
    from werkzeug.exceptions import abort
    File "C:\Users\carly.virtualenvs\two-factor-auth-flask-master-syG0u3oG\lib\site-packages\werkzeug__init__.py", line 151, in <module>
    import('werkzeug.exceptions')
    File "C:\Users\carly.virtualenvs\two-factor-auth-flask-master-syG0u3oG\lib\site-packages\werkzeug\exceptions.py", line 71, in <module>
    from werkzeug.wrappers import Response
    File "C:\Users\carly.virtualenvs\two-factor-auth-flask-master-syG0u3oG\lib\site-packages\werkzeug\wrappers.py", line 26, in <module>
    from werkzeug.http import HTTP_STATUS_CODES, \
    File "C:\Users\carly.virtualenvs\two-factor-auth-flask-master-syG0u3oG\lib\site-packages\werkzeug\http.py", line 1044, in <module>
    from werkzeug.datastructures import Accept, HeaderSet, ETags, Authorization, \
    File "C:\Users\carly.virtualenvs\two-factor-auth-flask-master-syG0u3oG\lib\site-packages\werkzeug\datastructures.py", line 16,
    in <module>
    from collections import Container, Iterable, MutableSet
    ImportError: cannot import name 'Container' from 'collections' (C:\Python\lib\collections__init__.py)
    (two-factor-auth-flask-master) PS C:\Users\carly\two-factor-auth-flask-master> python app.py
    Traceback (most recent call last):
    File "C:\Users\carly\two-factor-auth-flask-master\app.py", line 4, in <module>
    from flask import Flask, render_template, redirect, url_for, flash, session, \
    File "C:\Users\carly.virtualenvs\two-factor-auth-flask-master-syG0u3oG\lib\site-packages\flask__init__.py", line 17, in <module>
    from werkzeug.exceptions import abort
    File "C:\Users\carly.virtualenvs\two-factor-auth-flask-master-syG0u3oG\lib\site-packages\werkzeug__init__.py", line 151, in <module>
    import('werkzeug.exceptions')
    File "C:\Users\carly.virtualenvs\two-factor-auth-flask-master-syG0u3oG\lib\site-packages\werkzeug\exceptions.py", line 71, in <module>
    from werkzeug.wrappers import Response
    File "C:\Users\carly.virtualenvs\two-factor-auth-flask-master-syG0u3oG\lib\site-packages\werkzeug\wrappers.py", line 26, in <module>
    from werkzeug.http import HTTP_STATUS_CODES, \
    File "C:\Users\carly.virtualenvs\two-factor-auth-flask-master-syG0u3oG\lib\site-packages\werkzeug\http.py", line 1044, in <module>
    from werkzeug.datastructures import Accept, HeaderSet, ETags, Authorization, \
    File "C:\Users\carly.virtualenvs\two-factor-auth-flask-master-syG0u3oG\lib\site-packages\werkzeug\datastructures.py", line 16,
    in <module>
    from collections import Container, Iterable, MutableSet
    ImportError: cannot import name 'Container' from 'collections' (C:\Python\lib\collections__init__.py)

  • #48 Carlyle Adams said

    I just answered my own question.
    After updating all requirements it runs.
    Sorry for the hassle.

  • #49 Hamid Allaoui said

    Thanks for this tutorial.

  • #50 edmartt said

    Hi, Miguel. Thank you for this.

    I see a problem here.

    When you are in development environment some errors may occur, and if something goes wrong at the time you use the register form, the user will be added but no qr code will be showed.

    Another thing here is, I'm trying to add some validation for deleting the session username, cause for any reason the page could be reloaded and, again, the user will be added and you won't get access to this added account. Any advice here?

Leave a Comment