How to add an additional layer of security to the Flask app using Google Authenticator. Which requires the addition of a page in-between the login page and the end page the user is aiming to access.
pip install qrcode
pip install pyotp
The QRcode package is issued to generate the image (typically a PNG file) and render the QR codes directly to the console. In this case, we will send the file name to our HTML page to be rendered.
The pyotop package is used the generate the one-time password and is used to implement two-factor authentication in web applications. Further information is provided in the article pyotp 2.9.0.
from flask import Flask, render_template, request, redirect, url_for, session
import userManagement as dbHandler
import pyotp
import pyqrcode
import os
import base64
from io import BytesIO
Start by adding a secret key variable at the beginning of your Python file. This will be used to securely sign the session cookies and can be used for any other security-related needs.
app = Flask(__name__)
app.secret_key = 'my_secret_key'
We now need to redirect (update) the login button, when successful, to the 2FA page, which will display the QRcode and ask for the code (return redirect(url_for('enable_2fa')) #redirect to 2FA page).
We also need to add in code to generate the one-time passcode (user_secret = pyotp.random_base32()).
def home():
user_secret = pyotp.random_base32() #generate the one-time passcode
return redirect(url_for('enable_2fa')) #redirect to 2FA page
Create the index HTML page within the template folder and name it index.html.
We then need to insert the code below, which displays the QRCode generated by the Python file, renderings it to the HTML using the IMG tag and requests that the one-time passcode be entered using a form.
This tag in HTML adds the QRcode generated in the Python file. Make sure the name used within the { } brackets, in this case, qr_code, matches the one created in the Python file.
<img src="data:image/png;base64,{{ qr_code }}">
<h1>Welcome Enable 2FA {{ value }}!</h1>
<h1>Scan this QR Code with Google Authenticator</h1>
<img src="data:image/png;base64,{{ qr_code }}">
We then need to add a form to the page to get the code entered by the user after they scan the QRcode.
Again, make sure the name you assign to the input matches the one used in the Python file, in this case, OTP.
<form action="/index.html" method="post">
<label for="otp">Enter the OTP from your app:</label><br>
<input type="text" id="otp" name="otp"><br>
<input type="submit" value="Enable 2FA">
</form>
Create routes to handle HTTP requests (like GET and POST) when a user submits the one-time passcode.
@app.route('/index.html', methods=['POST', 'GET'])
@app.route('/', methods=['POST', 'GET'])
In this section of code, we need to generate a secret key for the user.
def home():
user_secret = pyotp.random_base32()
We are now going to generate the QRCode and one-time passcode.
The line of code below is used generate the one time passcode based on the secret key generated in the previous step. This will then be used to generate the QRCode using the inbuilt function totp.provisioning_uri(name=username,issuer_name="YourAppName")
.
totp = pyotp.TOTP(user_secret)
Note: the name of the image created is qr_code.png. Remember this name needs to match the one used in the HTML file.
The line, qr_code.png(stream, scale=5)
, allows you to adjust the size of the QR Code
The final line of code is used to encode binary data into printable ASCII characters and decoding such encodings back to binary data.
totp = pyotp.TOTP(user_secret)
otp_uri = totp.provisioning_uri(name=username,issuer_name="YourAppName")
qr_code = pyqrcode.create(otp_uri)
stream = BytesIO()
qr_code.png(stream, scale=5)
qr_code_b64 = base64.b64encode(stream.getvalue()).decode('utf-8')
All that is left is to validate the entry of the one-time passcode and redirect the user to the desired page.
In this section we are retrieving the input from the form using the line of code:
otp_input = request.form[‘otp’]
We then use the inbuilt function totp.verify
to validate the code entered matches the one generated by Google Authenticator app. If the valid code matches, we direct the user to the desired page, as they have successfully logged on using 2FA.
Note, you will need to either created a new some_page.html page or change the name to the page you wish to display.
You may also wish to create a page which indicates the code entered is invalid as currently it simply displays a plain message to a blank page.
if request.method == 'POST':
otp_input = request.form['otp']
if totp.verify(otp_input):
return render_template('some_page.html')
#return redirect(url_for('home')) # Redirect to home if OTP is valid
else:
return "Invalid OTP. Please try again.", 401
return render_template(index.html')
python app.py
Finally, run the Flask app, and test the newly created page using the Google Authenticator app. Note, you may need to rescan the qrcode every time modifications are made to the code.