import ReactPlayer from 'react-player'
Multi-factor authentication (MFA) is an application security feature. Applications that store sensitive information, such as financial or healthcare details, use MFA to secure access to sensitive data. MFA requires users to enter information beyond a username/password to login to an application. This additional layer provides additional protection to user data after a username/password is stolen.
Application code can implement MFA in several ways. An MFA implementation might require the user to answer a security question, enter a code sent to their phone or email, scan a fingerprint, and more. On this page, you will learn how integrate the Google Authentication service code into a simple application to provide it with MFA.
Make sure you have an understanding of the how the application should behave and the software environment you will need to build it.
The Google Authenticator app is a mobile device app that supplies a user with a Time-based-One-Time Password (TOTP). Users must have the authenticator software on their device. When a user logs into the web application it determines if the user is logging in for the first time.
View as flowchart
---
config:
look: classic
---
flowchart TD
%% Nodes
A[Log in with username/password]
B{First login?}
C[Provide QR code for setup]
D[User Scans QR code]
E[Authenticator provides OTP ]
F[User verifies OTP in web app]
G{Is OTP correct?}
H[User authenticated]
I[Display Home page]
J[Access denied]
%% Edges
A --> B
B -- Yes --> C --> D --> E --> F --> G -- Continue login --> H --> I
B -- No --> E
G -- No --> J --> E
%% Styling (Optional)
style A color:#FFFFFF, fill:#1F77B4
style B color:#FFFFFF, fill:#FF7F0E
style C color:#FFFFFF, fill:#2CA02C
style D color:#FFFFFF, fill:#2CA02C
style E color:#FFFFFF, fill:#2CA02C
style F color:#FFFFFF, fill:#2CA02C
style G color:#FFFFFF, fill:#2CA02C
style H color:#FFFFFF, fill:#1F77B4
style I color:#FFFFFF, fill:#FF7F0E
style J color:#FFFFFF, fill:#2CA02C
First time users must set up the Authenticator. The application provides a QR code to make set up easier for users. After scanning the QR code, the Authenticator adds an entry for the web app to its list. The authenticator generates an OTP continually every 30 seconds.
Once setup, users logging into the app first supply first a username/password and then are prompted to enter a One-Time-Password (OTP) from the authenticator. If the correct code is provided to the application, the user is authenticated and app displays the Home page.
The procedures on this page rely on a simple application written in Python and using the Flask web framework. To work through this example, you should have a working knowledge of the Linux command line, GitHub, Git, Python, and pip. You will need a text editor to edit and save program files.
You will need the Google Authenticator app installed on your phone to test your work.
:::note The sample code was tested on a MacBook Pro running MacOS Ventura 13.6.6, Python 3.11.4, and the Flask web framework version 3.0.3. If your environment differs significantly, you may need to adjust your environment for the code to work. :::
In this procedure, you install code for, and then run, a simple web application. The application is secured only with a username/password. Completing this procedure will confirm your local machine environment is set up correctly.
-
Open a terminal on your local machine.
-
Clone the simple_web_app to a directory.
To clone with HTTPS:
git clone https://github.com/moxiegirl/simple_web_app.git
-
Change to the root of the
simple_web_app
directory created by cloning.You should see the following content in the directory:
. ├── README.md ├── app.py └── templates ├── home.html ├── login.html
The
app.py
file contains the logic for the application. Thetemplates
directory contains the application pages. -
Run the
app.py
application.python app.py
Python starts a server running the application on an open address and port.
* Serving Flask app 'app' * Debug mode: on WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Running on http://127.0.0.1:5000 Press CTRL+C to quit * Restarting with watchdog (fsevents) * Debugger is active! * Debugger PIN: 618-658-422
-
Paste the URL to your browser's address field.
Your browser should display this screen:
The web application has two logins configured:
users = { "user1": "password1", "user2": "password2" }
-
Test each username/password combination and confirm they succeed.
In your terminal, make sure you are still in the root of the simple_web_app
repository. Then, do the following:
-
Install the
pyotp
library which is used for generating and validating the TOTP.pip install pyotp
-
Install the
qrcode
library which is used to generate QR codes.pip install qrcode[pil]
-
Open the
app.py
file in your text editor. -
Expand the
imports
to include three additional libraries.import pyotp import qrcode import io
The
io
library contains tools to handle reading and writing data to files or to the in-memory buffer. The web application will use this library to save a QR code to an in-memory buffer. -
Update the
flask
import to include thesend_file
function.from flask import Flask, render_template, redirect, url_for, request, session, send_file
This function is used to send a QR code to the user's browser for display.
-
Update the
users
dictionary to include asecret
value.users = { "user1": {"password": "password1", "secret": None}, "user2": {"password": "password2", "secret": None} }
This value holds the MFA secret key that is created when a user provides the proper code from the Google Authenticator. Users with a
secret
value ofNone
are first time users. -
Add a function to generate the TOTP secret.
def generate_totp_secret(): return pyotp.random_base32()
This secret is shared between the web application and the authenticator application, in this case, the Google Authenticator. Both applications use the same secret and the current timestamp to generate one-time-passwords (OTPs). This ensures that both your application and the authenticator generate the same OTP for any given time window (for example, a 30-second window).
When the user logs in to your application, they enter the OTP displayed by authenticator app. Your application uses the secret to generate the expected OTP and verifies the generated value matches the one from the authenticator.
-
Add a
qrcode
route for generating a QR code.@app.route('/qrcode') def serve_qr_code(): if 'username' not in session: return redirect(url_for('login')) username = session['username'] secret = users[username]['secret'] totp = pyotp.TOTP(secret) uri = totp.provisioning_uri(username, issuer_name="My_MFA_App") # Generate the QR code and serve as an image img = qrcode.make(uri) buf = io.BytesIO() img.save(buf, format='PNG') buf.seek(0) return send_file(buf, mimetype='image/png')
This route checks if the user has a session in the application. For users with a session, the application builds a QR code image and stores it in a buffer. This image is served later from another route. Line 9 sets up the
issuer_name
that becomes your application's label in the Google Authenticator. -
Update the
login
route to check for a previously set MFA secret.@app.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': username = request.form['username'] password = request.form['password'] if username in users and users[username]["password"] == password: session['username'] = username # Check if MFA secret is already set, skip to code verification if users[username]['secret']: return redirect(url_for('verify_mfa')) else: # Generate a new secret if none exists and go to MFA setup users[username]['secret'] = generate_totp_secret() return redirect(url_for('mfa_setup')) else: return "Invalid credentials, please try again." return render_template('login.html')
This
login
route must determine if a user has already set up with an authenticator app or not. A user with a setup can simply supply a new code to verify, so the code sends those users to theverify_mfa
route. A user without a setup, is directed to create one in the authenticator via themfa_setup
route. -
Add the
mfa_setup
route.@app.route('/mfa_setup') def mfa_setup(): if 'username' not in session: return redirect(url_for('login')) # Render the MFA setup page return render_template('mfa_setup.html')
The
mfa_setup.html
template (you will add this later) displays the image generated by the previousqrcode
route. -
Add the
verify_mfa
route.@app.route('/verify_mfa', methods=['GET', 'POST']) def verify_mfa(): if 'username' not in session: return redirect(url_for('login')) if request.method == 'POST': username = session['username'] secret = users[username]['secret'] token = request.form.get('token') # Check if the token was entered if not token: return render_template('verify_mfa.html', error="Please enter your token") totp = pyotp.TOTP(secret) # Verify the TOTP token entered by the user if totp.verify(token): return render_template('home.html', username=username) else: return render_template('verify_mfa.html', error="Invalid token, please try again.") return render_template('verify_mfa.html')
This route verifies the OTP a user entered after receiving it from the authenticator app. A valid token completes the user's authentication with the app, the application grants access and displays the home page.
At this point, you have completed the logic for MFA in your main application.
Get the complete `app.py` file
import pyotp
import qrcode
import io
from flask import Flask, render_template, redirect, url_for, request, session, send_file
app = Flask(__name__)
app.secret_key = 'your_secret_key'
# In-memory user storage for simplicity
users = {
"user1": {"password": "password1", "secret": None},
"user2": {"password": "password2", "secret": None}
}
# Function to generate a TOTP secret
def generate_totp_secret():
return pyotp.random_base32()
# Route to generate and serve the QR code
@app.route('/qrcode')
def serve_qr_code():
if 'username' not in session:
return redirect(url_for('login'))
username = session['username']
secret = users[username]['secret']
totp = pyotp.TOTP(secret)
uri = totp.provisioning_uri(username, issuer_name="MFA App")
# Generate the QR code and serve as an image
img = qrcode.make(uri)
buf = io.BytesIO()
img.save(buf, format='PNG')
buf.seek(0)
return send_file(buf, mimetype='image/png')
@app.route('/')
def home():
if 'username' in session:
return render_template('home.html', username=session['username'])
return redirect(url_for('login'))
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
if username in users and users[username]["password"] == password:
session['username'] = username
# Check if MFA secret is already set, skip to code verification
if users[username]['secret']:
return redirect(url_for('verify_mfa'))
else:
# Generate a new secret if none exists and go to MFA setup
users[username]['secret'] = generate_totp_secret()
return redirect(url_for('mfa_setup'))
else:
return "Invalid credentials, please try again."
return render_template('login.html')
@app.route('/mfa_setup')
def mfa_setup():
if 'username' not in session:
return redirect(url_for('login'))
# Render the MFA setup page
return render_template('mfa_setup.html')
@app.route('/verify_mfa', methods=['GET', 'POST'])
def verify_mfa():
if 'username' not in session:
return redirect(url_for('login'))
if request.method == 'POST':
username = session['username']
secret = users[username]['secret']
token = request.form.get('token')
# Check if the token was entered
if not token:
return render_template('verify_mfa.html', error="Please enter your token")
totp = pyotp.TOTP(secret)
# Verify the TOTP token entered by the user
if totp.verify(token):
return render_template('home.html', username=username)
else:
return render_template('verify_mfa.html', error="Invalid token, please try again.")
return render_template('verify_mfa.html')
@app.route('/logout')
def logout():
session.pop('username', None)
return redirect(url_for('login'))
if __name__ == '__main__':
app.run(debug=True)
In the next section, you create pages for each URL mentioned in the routes.
You need to add two new files to the templates
directory in the app. Using
your text editor, do the following:
-
Create a
templates/mfa_setup.html
file and add the following code:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>MFA Setup</title> </head> <body> <h1>Scan this QR Code with Google Authenticator</h1> <img src="{{ url_for('serve_qr_code') }}" alt="QR Code" width="150"> <p>After scanning, click below to verify your code.</p> <!-- Changed method to POST --> <form action="{{ url_for('verify_mfa') }}" method="POST"> <input type="submit" value="Proceed to Code Verification"> </form> </body> </html>
-
Create a
templates/verify_mfa.html
file and add the following code:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0" > <title>Verify MFA Code</title> </head> <body> <h1>Enter the Code from Google Authenticator</h1> {% if error %} <p style="color: red;">{{ error }}</p> {% endif %} <form method="POST"> <label for="token">Token:</label> <input type="text" name="token" id="token" required> <input type="submit" value="Verify"> </form> </body> </html>
At this point, your application is functionally complete.
-
Make sure you have the Google Authenticator app installed on your mobile device.
-
Start your web application and test it.
python app.py
The following video shows a successful web application test.
The Googl Authenticator should show an entry for the web application:
You can get the completed sample application code as a zip or via a clone from this repository.