Or press ESC to close.

Automate 2FA: TOTP Authentication with QR Code Decoding

Oct 27th 2024 17 min read
medium
ui
python3.12.5
playwright1.48.0
authentication

Two-factor authentication (2FA) is essential for securing user accounts. Time-based One-Time Passwords (TOTP) are a popular 2FA method, using a dynamic passcode that adds a layer of security beyond just a password. However, manually testing 2FA can be a tedious process for QA teams, especially when scanning QR codes and entering OTPs within strict time windows.

In this blog post, we'll explore how to fully automate 2FA testing with TOTP, using Python, Playwright, and QR code decoding. By the end, you'll have a streamlined approach to handle 2FA authentication in your automated tests efficiently.

Why Automate 2FA?

2FA is crucial for securing user accounts, but it presents unique challenges when it comes to testing. Manually testing 2FA workflows is often time-consuming and prone to errors, requiring testers to retrieve and input time-sensitive OTPs accurately within a short window. For every test run, testers must wait for an OTP to regenerate, re-enter it, and hope there are no missteps. This repetitive, hands-on approach makes manual 2FA testing inefficient, especially when testing at scale or when automated testing frameworks handle other aspects of an application.

Automating 2FA provides key benefits for reliable and efficient testing. With automation, OTPs can be generated and validated quickly, minimizing the likelihood of human error and freeing up valuable time for the QA team. Automating the TOTP generation and validation process ensures that authentication workflows are verified consistently across multiple test scenarios. This enhances test reliability and increases overall testing speed, making it easier to identify potential issues in 2FA integration early on. Additionally, 2FA automation enables easier integration of security testing into continuous integration and deployment (CI/CD) pipelines, ensuring that 2FA functionality is tested automatically on every code change.

By automating the testing of TOTP-based 2FA, teams can maintain robust security standards while streamlining their workflows, ultimately improving the quality and security of their applications.

Tools and Libraries Needed

To automate 2FA using TOTP, we'll need a few key tools and libraries:

To install these libraries, use the following commands:

                
pip install playwright pyotp pyzbar pillow
                

Test Application

The testing app for this tutorial is a web-based login application designed to demonstrate 2FA using TOTP. The login process requires users to enter a username and password initially.

login page

Login page of the testing application

Upon successful entry, the application generates a unique QR code, containing a TOTP secret, which the user must scan to complete their login.

OTP verification page

OTP verification page of the testing application

The 2FA step, reliant on this QR code, ensures that a unique OTP is generated every 30 seconds, enhancing the security of the login process.

Successful login page

Successful login page of the testing application

Step 1: Capturing the QR Code

After submitting the username and password, the app displays a QR code for 2FA verification. Using Playwright, we can capture this QR code directly from the page by taking a screenshot of the specific element.

                
page.wait_for_selector("#qr-code", timeout=5000)
page.locator("#qr-code").screenshot(path="qr_code_image.png")
print("QR code saved as: qr_code_image.png")
                

This snippet waits until the QR code appears, then captures it as an image file named qr_code_image.png. We'll use this image to extract the TOTP secret key in the next step.

Step 2: Extracting the TOTP Secret from the QR Code

With the QR code image saved, we can use the pyzbar library to decode the image and retrieve the URL that contains the TOTP secret. This secret allows us to generate a time-based OTP.

                
from pyzbar.pyzbar import decode
from PIL import Image
import re

img = Image.open("qr_code_image.png")

decoded_objects = decode(img)
            
for obj in decoded_objects:
    qr_text = obj.data.decode('utf-8')

match = re.search(r"secret=([A-Z2-7]+)", qr_text)
secret_key = match.group(1) if match else None
                

First, the code loads the QR image and uses Pyzbar's decode function to interpret its contents. Then, it retrieves the decoded text, which is typically a URL containing a secret parameter. By using a regular expression, the code locates and isolates the value of this secret parameter, storing it in the secret_key variable. This secret key is essential for generating TOTPs for the authentication process.

Step 3: Generating the OTP

Once we have the secret key, we can generate a time-based OTP using pyotp. This OTP will be valid for a limited time, ensuring it matches the server's current TOTP.

                
import pyotp

totp = pyotp.TOTP(secret_key)
otp = totp.now()
print(f"Generated OTP: {otp}")
                

The totp.now() function generates an OTP based on the current time, valid for the short time window defined by the TOTP algorithm.

Step 4: Automating the OTP Login Process

Now that we have the OTP, we can complete the login by entering the OTP along with the username and password. If OTP validation fails, we can handle this with retries or error messages.

                
page.fill("#username", username)
page.fill("#password", password)
page.click("#login-button")
                    
page.fill("#otp", otp)
page.click("#verify-button")
                    
page.wait_for_selector("#login-success-message", timeout=10000)
print("Login successful!")
                

This snippet fills in the username, password, and OTP, simulating the entire 2FA login process in an automated fashion.

Putting It All Together

Below is the full integration of all previous steps, creating a streamlined automation script for 2FA login.

                
def login_with_otp(username, password):
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=False)
        page = browser.new_page()
                
        try:
            page.goto("http://localhost:3000/login")
            page.fill("#username", username)
            page.fill("#password", password)
            page.click("#login-button")
                
            page.wait_for_selector("#qr-code", timeout=5000)
            page.locator("#qr-code").screenshot(path="qr_code_image.png")
                
            secret_key = extract_secret_from_qr("qr_code_image.png")
                
            if secret_key:
                otp = generate_otp(secret_key)
                page.fill("#otp", otp)
                page.click("#verify-button")
                page.wait_for_selector("#login-success-message", timeout=10000)
                print("Login successful!")
            else:
                print("Failed to extract secret key from QR code.")
        finally:
            browser.close()
                

This complete script navigates through each step: logging in, capturing and decoding the QR code, generating an OTP, and completing the login process. It handles errors if the QR code or OTP is not valid, ensuring a robust and reliable 2FA automation solution.

Best Practices for 2FA Automation

When automating 2FA testing, it's crucial to follow best practices to ensure both security and reliability:

1. Securely Handle QR Code Images

QR code images used for generating TOTP secrets often contain sensitive information. To prevent unauthorized access, consider encrypting or temporarily storing these images, and delete them once the test completes. Use secure file permissions to prevent other processes from accessing QR images during tests.

2. Avoid Exposing Sensitive Information

Ensure that no sensitive information, such as usernames, passwords, or TOTP secrets, is logged or stored in test logs or reports. Use environment variables to securely manage credentials, and avoid hard-coding sensitive data in your scripts.

3. Testing OTP Expiration and Clock Skew

OTPs are time-sensitive, typically valid for only 30 seconds. Include tests for expired OTPs by deliberately using an old OTP, which should trigger a login failure. Additionally, consider testing scenarios where the server or client time might drift by a few seconds to ensure your authentication system handles minor clock skew gracefully.

4. Avoid Using Real User Accounts

When testing in a staging environment, create dedicated test accounts with 2FA enabled to avoid impacting actual user accounts. Mock or isolate your test environment from production services to ensure test data doesn't interfere with real user data or authentication.

5. Implement Retries Sparingly

For automation reliability, implement limited retries if the OTP verification occasionally fails, but use this sparingly. Excessive retries might indicate an issue with time synchronization or test stability. Address root causes before increasing retry counts to avoid unreliable test outcomes.

Conclusion and Next Steps

Automating 2FA testing with TOTP is a powerful way to streamline authentication verification in secure applications. This guide walked through each step - from capturing QR codes to extracting secrets and automating OTP input - to build a reliable 2FA test workflow. With this approach, we can save time and reduce human error, enhancing the security and stability of our testing process.

For further enhancement, consider integrating this setup into a CI/CD pipeline to automate 2FA tests in your continuous deployment flow, helping to detect issues early and maintain secure, high-quality releases. If you'd like to try out this approach, the complete code example with added error handling, as well as a test application with setup instructions, is available on our GitHub page. We encourage you to explore the example, adapt it to your testing needs, and enhance your authentication testing setup today!