Multi-Factor Authentication (MFA)

Multi-factor authentication (MFA) is a security mechanism that requires users to provide two or more forms of authentication to access a system or application. This additional layer of security makes it harder for hackers and cybercriminals to gain unauthorized access to sensitive information. In this article, we will provide a step-by-step guide on how to implement multi-factor authentication.

In this article

Step 1: Choose the Right MFA Solution

The first step in implementing MFA is to choose the right solution for your organization. Each of these solutions has its own strengths and weaknesses, so it’s important to choose the one that best suits your organization’s needs.

There are several types of MFA solutions available, each with their own strengths and weaknesses. Some common types of MFA solutions include:

  • SMS-based authentication: This method involves sending a one-time code to the user’s mobile device via SMS. While this method is easy to use and convenient, it’s also vulnerable to attacks such as SIM swapping, where an attacker can take control of the user’s mobile number and intercept the one-time code.
  • Time-based One-Time Password (TOTP): This method involves using a mobile app like Google Authenticator or Microsoft Authenticator to generate a one-time code that expires after a certain amount of time. This method is more secure than SMS-based authentication, as it’s not vulnerable to SIM swapping attacks. However, it requires users to have a smartphone and install a third-party app.
  • Hardware tokens: These are physical devices that generate one-time codes, such as a USB token or a smart card. This method is very secure, as the user needs to physically possess the token to access their account. However, it can be expensive to implement and can be inconvenient for users who need to carry the token around with them.
  • Biometric authentication: This involves using a user’s unique physical characteristics, such as fingerprints, facial recognition, or voice recognition, to authenticate them. This method is very secure, as it’s difficult for an attacker to replicate someone’s biometric data. However, it can be expensive to implement and can be vulnerable to false positives or false negatives.

When choosing an MFA solution, it’s important to consider factors such as cost, ease of use, and security. It’s also a good idea to consult with your IT team or a security expert to determine the best solution for your organization.

Step 2: Determine Which Accounts Require MFA

Once you’ve chosen an MFA solution, the next step is to determine which accounts require MFA. Generally, you’ll want to require MFA for any account that has access to sensitive information or controls important functions within your organization. This might include:

  • Email accounts
  • Online banking accounts
  • Customer databases
  • Administrative accounts
  • VPN access

It’s also a good idea to require MFA for any account that has been previously compromised or has a history of weak or easily guessable passwords.

Step 3: Educate Your Users

It’s important to educate your users about the benefits of MFA and how to use it properly. You should provide clear instructions on how to set up and use the MFA solution you’ve chosen, and encourage users to enable MFA for all of their accounts.

Some key points to emphasize when educating your users about MFA include:

  • The importance of using strong, unique passwords
  • The benefits of using MFA to add an extra layer of security to their accounts
  • How to set up and use the MFA solution you’ve chosen

It’s also a good idea to encourage users to enable MFA for all of their accounts, not just those that require it.

Step 4: Configure Your System to Support MFA

Before you can enable MFA for your users, you’ll need to configure your system to support it. This involves setting up the appropriate authentication protocols and integrating the MFA solution with your existing authentication system.

Depending on the MFA solution you’ve chosen, this might involve:

  • Installing a plugin or extension for your existing authentication system
  • Setting up a new authentication server or service
  • Configuring your existing authentication system to support the appropriate authentication protocols (such as OAuth, SAML, or OpenID Connect)

Step 5: Enforce MFA

Once you’ve configured your system to support MFA, it’s time to enforce it. You should require all users to enable MFA for any account that requires it, and ensure that users cannot bypass MFA by using weak or easily guessable passwords.

Finally, it’s important to monitor your system for any MFA-related issues, such as users being locked out of their accounts or problems with the MFA solution itself. You should have a plan in place to quickly resolve any issues that arise and ensure that your system remains secure.

In conclusion, implementing multi-factor authentication is an essential step in securing your organization’s sensitive information and data. By following the steps outlined in this article, you can successfully implement MFA and help protect your organization from cyber threats.

Coding Examples

In this section we area going through some code examples for implementing MFA in Python using the TOTP method with the pyotp library. PyOTP is a Python library for generating and verifying one-time passwords. It can be used to implement two-factor (2FA) or multi-factor (MFA) authentication methods in web applications and in other systems that require users to log in. (more info here https://pypi.org/project/pyotp/ and here https://github.com/pyauth/pyotp)

Install pyotp and qrcode

First of all, we have to install the pyotp library and this can be done by using pip

pip install pyotp qrcode

Generate the TOTP secret

To enable MFA for a user account, you’ll need to generate a TOTP secret. This secret is a base32-encoded string that is used to generate one-time codes. You can generate a TOTP secret using the pyotp library and then render in a QR code format

import pyotp

totp_secret = pyotp.random_base32()
print("TOTP Secret: {}".format(totp_secret))

Generating a TOTP Code

Once you’ve generated a TOTP secret, you can use it to generate a one-time code. This code is based on the current time and the TOTP secret, and is only valid for a short period of time (usually 30 seconds). You can generate a TOTP code using the pyotp library:

import pyotp

# The TOTP secret is the key shared by the authenticator entity and the authenticating user
totp_secret = "ABC123DEF456GHI789JKL"

# Create a TOTP object
totp = pyotp.TOTP(totp_secret)

# Generate a TOTP code
totp_code = totp.now()

# Print the code
print("TOTP Code:", totp_code)

Validating a TOTP Code

When a user logs in using MFA, they will be prompted to enter a TOTP code. To validate this code, you can compare it to the code generated using the TOTP secret. If the codes match, the user is authenticated. You can validate a TOTP code using the pyotp library:

import pyotp
totp_secret = "ABC123DEF456GHI789JKL"

user_code = "123456"
totp = pyotp.TOTP(totp_secret)
if totp.verify(user_code):
    print("Code is valid!")
else:
    print("Code is not valid.")

Note that in a real-world scenario, you would typically store the TOTP secret securely and load it when needed (for example, from a database). You would also typically use a more secure method than printing the TOTP secret or code to the console. Additionally, you would need to integrate these code snippets into your existing authentication system.

Real world flow example

Now that we know all the little pieces of our jigsaw we need to put all of them together into a flow. In this section we are showing how all these small pieces can be joined together. In particular the flow should look similar to this

  • The user logs in to the application
  • The user decides to configure the MFA
    • generate a TOTP secret (store in a secure location, in our example will just be a file for simplicity)
    • generate the QR code to scan with the authenticator App
  • The user decides to login
    • the application once validated the creds (i.e. username and password) will ask a OTP (one time password)
    • the user will provide the OTP and this will be validated against the TOTP secret stored
  • Once all the pieces are considered valid we can then let the user in and access to the application and its related resources

I have worked out a script that might help you in understanding and experimenting this flow (you might need some additional libraries like PIL and qrcode)

import os
import argparse
import qrcode
import pyotp
from PIL import Image

APPS_DEFINED_FOLDER = "./secrets"
QR_CODE_IMG_FOLDER = "./img"

if not os.path.exists(APPS_DEFINED_FOLDER):
    print("Creating the APPS folder")
    os.mkdir(APPS_DEFINED_FOLDER)

if not os.path.exists(QR_CODE_IMG_FOLDER):
    print("Creating the QR IMG folder")
    os.mkdir(QR_CODE_IMG_FOLDER)

def open_image(image_path):
    try:
        generated_code = Image.open(image_path)
        generated_code.show()
    except Exception as e:
        print("Exception caught while opening the image: {}".format(e))

def write_key(r_key, issuer_name):
    with open("{}/{}".format(APPS_DEFINED_FOLDER, issuer_name), "w") as f:
        f.write(r_key)

def verify_otp(app_name, otp_code):
    verification = False
    try:
        with open("{}/{}".format(APPS_DEFINED_FOLDER, app_name), "r") as key_file:
            key = key_file.read()
            print(key)
            otp_verifier = pyotp.TOTP(key)
            verification = otp_verifier.verify(otp_code)
            
    except Exception as e:
        print("Unable to find the proper Application key")
    return verification

def create_qr_code(account_name, issuer_name, logo_image=None, base_width=100):
    image_save_path = "./imgs/img.png"
    image_logo = ""
    rand_32_string = pyotp.random_base32()
    provisioning_uri = pyotp.totp.TOTP(rand_32_string).provisioning_uri(
        account_name, 
        issuer_name
    )
    if logo_image :
        try:
            image_logo = Image.open(logo_image)
            # adjust image size
            wpercent = (base_width/float(image_logo.size[0]))
            hsize = int((float(image_logo.size[1])*float(wpercent)))
            image_logo = image_logo.resize((base_width, hsize), Image.Resampling.LANCZOS)
        except Exception as e:
            print("Exception found during the image opening: {}".format(e))

    q_img = qrcode.QRCode(
            error_correction=qrcode.constants.ERROR_CORRECT_H
        )
    q_img.add_data(provisioning_uri)

    QRcolor = "black"

    # adding color to QR code
    QRimg = q_img.make_image(
        fill_color=QRcolor, back_color="white").convert('RGB')
    
    # set size of QR code
    if logo_image :
        pos = ((QRimg.size[0] - image_logo.size[0]) // 2,
            (QRimg.size[1] - image_logo.size[1]) // 2)
        QRimg.paste(image_logo, pos)
    QRimg.save(image_save_path)
    return rand_32_string, image_save_path


def list_apps(path=APPS_DEFINED_FOLDER):
    if os.path.exists(path) and os.path.isdir(path):
        list_of_apps = os.listdir(path)
        if len(list_of_apps) == 0:
            print("No Apps defined")
            exit()
        for i in list_of_apps:
            print("App Name: {}".format(i))
        print("--------")

parser = argparse.ArgumentParser(
    description="",
    formatter_class=argparse.ArgumentDefaultsHelpFormatter)


parser.add_argument("-l", "--list", action="store_true", help="List the MFA Apps configured")
parser.add_argument("-a", "--authenticate", action="store", help="Authenticate against the app") 
parser.add_argument("-o", "--otp-code", action="store", help="The OTP code") 
parser.add_argument("-c", "--create", action="store_true", help="Create a new app") 
parser.add_argument("-n", "--account-name", action="store", help="Account Name") 
parser.add_argument("-i", "--issuer-name", action="store", help="Account Name")

args = parser.parse_args()
config = vars(args)


if config.get("list"):
    list_apps()

elif config.get("create"):
    print("Create Application")
    account_name = config.get("account_name", None)
    issuer_name = config.get("issuer_name", None)
    if issuer_name is None  or account_name is None:
        print("Not enough args")
    print("Configuring MFA for Account: {}, Issuer: {}".format(config.get("account_name"), config.get("issuer_name")))
    r_key, qr_img = create_qr_code(
        account_name=account_name, 
        issuer_name=issuer_name, 
        base_width=100)
    write_key(r_key, issuer_name)
    open_image(qr_img)
elif config.get("authenticate"):
    print("Authenticate against {}".format(config.get("authenticate")))
    otp_code = config.get("otp_code", None)
    if not otp_code:
        print("Missing code")
        exit()
    verification_result = verify_otp(config.get("authenticate"), otp_code)
    if verification_result:
        print ("Welcome")
    else:
        print("OTP verification failed")

The usage of this script is pretty straight

# list all the defined app (basically is an ls in the ./secret folder)
python mfa.py -l 

# create a new MFA configuration will need some extra params
# this will generate a QR code that needs to be scan with an authenticator app
# like google authenticator or microsoft authenticator
python mfa.py -c -n "<account-name>" -i "<issuer-name">
# example
# python mfa.py -c -n "d3@keepforyourself.com" -i "keepforyourselfBlog"

# authenticate the user
python mfa.py -a <app-name> -o <otp-code>

I hope this article has helped you to get a better understanding around the MFA and how it works, and in particular to make your app more secure.

If you liked comment, share and help us grow!

d3

d3 is an experienced Software Engineer/Developer/Architect/Thinker with a demonstrated history of working in the information technology and services industry. Really passionate about technology, programming languages and problem solving. He doesn't like too much the self celebration and prefers to use that time doing something useful ...i.e. coding

You may also like...