OTP one time password email testing

How to test MFA 2FA short-codes and login magic links. Integration testing using throwaway email accounts. Dummy mailbox server to capture codes.

Many modern web and mobile applications use modern user account authentication techniques that involve temporary passwords. These one-time passwords (OTP) are sent via email or SMS as part of 2FA two factor authentication in OAuth and SAML applications. Using a free email service we can test username and password authentication methods end-to-end in software applications.

What are OTP passwords?

One time email links are emails that are sent to a user of a website when they enter their email address during login. A server sends the email containing a link or passcode that expires after a short time. This password can be used by the user to sign into an application without requiring a typical permanent password.

Example usage of one time 2FA authentication

AWS provides the Cognito authentication service for logging users into applications using OAuth or SAML username and password. In a demo app we created for this post we can use a simple react app hosted at playground.mailslurp.com to sign up for an account on a dummy application. A verification code is then sent to your email address which can be entered into a confirmation screen to confirm the account.

cypress otp email

Automated OTP testing with CypressJS

We can test this demo app using Cypress JS and the MailSlurp email API.

npm install --save-dev cypress-mailslurp cypress

Then run npx cypress open to scaffold your tests.

Writing a test to receive OTP emails

The main steps for testing OTP are as follows:

  • Create a test email account
  • Sign up using a test email address
  • Wait for the email to arrive in the account
  • Extract the OTP code and submit it

We can automate that process using a Cypress end-to-end test like the one below.

Creating a dummy email address

Start your test by creating a throwaway email account.

// use cypress-mailslurp plugin to create an email address before test
before(function () {
    cy.log("Wrap inbox before test")
    return cy.mailslurp()
        .then(mailslurp => mailslurp.createInbox())
        .then(inbox => {
            cy.log(`Inbox id ${inbox.id}`)
            // save inbox id and email address to this (make sure you use function and not arrow syntax)
            cy.wrap(inbox.id).as('inboxId')
            cy.wrap(inbox.emailAddress).as('emailAddress')
        })
});

Load playground application in cypress

Next we need to load the app we are testing using cy.visit:

it("01 - can load the demo application", function () {
    // get wrapped email address and assert contains a mailslurp email address
    expect(this.emailAddress).to.contain("@mailslurp");
    // visit the demo application
    cy.visit("https://playground.mailslurp.com")
    cy.title().should('contain', 'React App');
});

Fill the login form with email address

Use the inbox email address and submit it to the test application.

// use function instead of arrow syntax to access aliased values on this
it("02 - can sign up using email address", function () {
    // click sign up and fill out the form
    cy.get("[data-test=sign-in-create-account-link]").click()
    // use the email address and a test password
    cy.get("[name=email]").type(this.emailAddress).trigger('change');
    cy.get("[name=password]").type('test-password').trigger('change');
    // click the submit button
    cy.get("[data-test=sign-up-create-account-button]").click();
});

Receive OTP username password via email

Once we submit the form wait for the code to arrive using the WaitForController methods. This method will hold the connection open for 30 seconds until the email arrives. It will throw an exception if the email does not arrive before then. Then we use a regex pattern to extract the OTP code and submit it.

it("03 - can receive confirmation code by email", function () {
    // app will send user an email containing a code, use mailslurp to wait for the latest email
    cy.mailslurp()
        // use inbox id and a timeout of 30 seconds
        .then(mailslurp => mailslurp.waitForLatestEmail(this.inboxId, 30000, true))
        // extract the confirmation code from the email body
        .then(email => /.*verification code is (\d{6}).*/.exec(email.body!!)!![1])
        // fill out the confirmation form and submit
        .then(code => {
            cy.get("[name=code]").type(code).trigger('change');
            cy.get("[data-test=confirm-sign-up-confirm-button]").click();
        })
});

Submit the confirmation code and test the welcome

Submit the email one-time password and assert the welcome page is shown.

// fill out sign in form
it("04 - can sign in with confirmed account", function () {
    // use the email address and a test password
    cy.get("[data-test=username-input]").type(this.emailAddress).trigger('change');
    cy.get("[data-test=sign-in-password-input]").type('test-password').trigger('change');
    // click the submit button
    cy.get("[data-test=sign-in-sign-in-button]").click();
});

Then we can see authorized welcome screen:

// can see authorized welcome screen
it("05 - can see welcome screen", function () {
    // click sign up and fill out the form
    cy.get("h1").should("contain", "Welcome");
});

Why test OTP?

By using disposable email addresses we can test OTP 2FA one time passwords in any real world application. Test your authentication username and password login for real using actual email addresses so you know that your application is functioning.

OTP testing