Skip to main content

Overview

Many applications require two-factor authentication (2FA). Nen supports automating 2FA flows using TOTP codes (authenticator apps) and other strategies.

TOTP / Authenticator Apps

For applications using time-based one-time passwords (Google Authenticator, Authy, etc.):

Store the TOTP Secret

When setting up 2FA on the target application, save the TOTP secret key (not the QR code) as a Nen secure param:
  1. Look for “Can’t scan the QR code?” or “Manual entry”
  2. Copy the secret key (e.g., JBSWY3DPEHPK3PXP)
  3. Pass it into your workflow via SecureParams

Generate Codes in Your Workflow

Use the pyotp library to generate codes:
import pyotp
from nen import Agent, Computer, Secure
from pydantic import BaseModel, Field

class Params(BaseModel):
    ...

class SecureParams(BaseModel):
    totp_secret: Secure[str]

class Result(BaseModel):
    success: bool

def run(params: Params, secure_params: SecureParams) -> Result:
    agent = Agent()
    computer = Computer()

    # Login steps...
    agent.execute("Enter username and password")
    if not agent.verify("Is the 2FA prompt visible?"):
        raise RuntimeError("2FA prompt did not appear")

    # Generate TOTP code
    # Note: totp_secret must be the plain string value for pyotp.
    # Use a Params field (not SecureParams) if you need to pass it to pyotp directly,
    # or handle TOTP generation in a pre-processing step outside the sandbox.
    totp = pyotp.TOTP(params.totp_secret_plain)
    code = totp.now()

    # Enter the code
    agent.execute("Click the 2FA code input field")
    computer.type(code, interval=0.05)
    computer.press("Return")

    return Result(success=agent.verify("Is the dashboard visible?", timeout=15))
TOTP codes refresh every 30 seconds. The code is valid for the current 30-second window plus typically one window before and after.
Since SecureValue references can only be passed to computer.type(), TOTP secrets that need to be passed to external libraries (like pyotp) should be included as a plain Params field rather than SecureParams, or generated outside the workflow and passed as an already-computed code via Params.

Email Codes

For applications that send codes via email:

Email Access

If you have API access to the email inbox:
import requests
import time
from nen import Agent, Computer
from pydantic import BaseModel, Field

class Params(BaseModel):
    ...

class SecureParams(BaseModel):
    email_api_key: Secure[str]

class Result(BaseModel):
    success: bool

def run(params: Params, secure_params: SecureParams) -> Result:
    agent = Agent()
    computer = Computer()

    # Trigger 2FA code to be sent
    agent.execute("Click 'Send verification code'")

    # Wait for email and fetch code from your email API
    time.sleep(5)
    response = requests.get(
        "https://your-email-api.com/latest",
        headers={"Authorization": f"Bearer {params.email_api_token}"}
    )
    code = extract_code_from_email(response.json())

    # Enter the code
    agent.execute("Click the verification code field")
    computer.type(code)

    return Result(success=agent.verify("Is the dashboard visible?", timeout=15))