Skip to content

Instantly share code, notes, and snippets.

@moxiegirl
Created September 5, 2024 19:56
Show Gist options
  • Save moxiegirl/8347ff5a30223133334180e647faf645 to your computer and use it in GitHub Desktop.
Save moxiegirl/8347ff5a30223133334180e647faf645 to your computer and use it in GitHub Desktop.
Coding for a Sample file

import ReactPlayer from 'react-player'

Implement Multi-factor Authentication (MFA)

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.

Before you begin

Make sure you have an understanding of the how the application should behave and the software environment you will need to build it.

Expected application behavior

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
Loading

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.

Software required for coding

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. :::

Confirm your local environment

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.

  1. Open a terminal on your local machine.

  2. Clone the simple_web_app to a directory.

    To clone with HTTPS:

    git clone https://github.com/moxiegirl/simple_web_app.git
  3. 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. The templates directory contains the application pages.

  4. 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
  5. Paste the URL to your browser's address field.

    Your browser should display this screen:

    Login screen

    The web application has two logins configured:

    users = {
        "user1": "password1",
        "user2": "password2"
    }
  6. Test each username/password combination and confirm they succeed.

    Home screen

Add the MFA code to app.py

In your terminal, make sure you are still in the root of the simple_web_app repository. Then, do the following:

  1. Install the pyotp library which is used for generating and validating the TOTP.

    pip install pyotp
  2. Install the qrcode library which is used to generate QR codes.

    pip install qrcode[pil]
  3. Open the app.py file in your text editor.

  4. 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.

  5. Update the flask import to include the send_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.

  6. Update the users dictionary to include a secret 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 of None are first time users.

  7. 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.

  8. 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.

  9. 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 the verify_mfa route. A user without a setup, is directed to create one in the authenticator via the mfa_setup route.

  10. 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 previous qrcode route.

  11. 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.

Add the MFA templates and test

You need to add two new files to the templates directory in the app. Using your text editor, do the following:

  1. 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>
  2. 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.

  3. Make sure you have the Google Authenticator app installed on your mobile device.

  4. 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:

Want the complete finished code?

You can get the completed sample application code as a zip or via a clone from this repository.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment