Test MFA with virtual TOTP devices
Documentation navigation
Pair a MailSlurp virtual authenticator with a test user's secret, generate fresh TOTP codes, and verify MFA enrollment and later sign-in.
MailSlurp virtual TOTP devices let automated tests complete authenticator challenges. Pair a device with the secret provided by your application's MFA enrollment flow, request a time-based code, and enter it using your browser or API test.
TOTP does not wait for an incoming message. Email OTP and SMS OTP arrive through an inbox or phone number; an authenticator generates its code from a shared secret and the current time.
Info: Choose the right MFA technique. Use the email and SMS testing guide for delivered verification codes. Use this guide when your application asks for a code from an authenticator app.
Understand the pairing values
| Value | Purpose |
|---|---|
| Base32 secret | Shared key issued during MFA enrollment. The application and virtual authenticator must use the same key. |
otpauth:// URL |
Enrollment URI containing the secret and authenticator parameters. |
| QR code | An encoded enrollment URI, often displayed by the identity provider. |
| TOTP device ID | MailSlurp identifier used to request codes from the paired virtual device. |
| TOTP code | Short-lived code submitted to the application's current MFA challenge. Keep it as a string. |
Use the secret from the test user's actual enrollment. Generating an unrelated device will produce codes the application does not accept. Treat enrollment secrets and URLs as credentials in test fixtures and reports.
Test enrollment and later sign-in
Begin MFA enrollment for your test user
-> Read the enrollment secret or otpauth URL
-> Create a paired MailSlurp TOTP device
-> Request a fresh code
-> Submit it to finish enrollment
-> Assert enrollment succeeded
-> Sign out and begin a new sign-in
-> Request a fresh code from the same device
-> Submit it and assert authenticated access
Enrollment and sign-in are separate assertions. A test that only accepts the first enrollment code has not yet verified a later MFA login.
Create a client
Install mailslurp-client and set MAILSLURP_API_KEY in your test environment:
import assert from 'node:assert/strict';
import { MailSlurp } from 'mailslurp-client';
const apiKey = process.env.MAILSLURP_API_KEY;
if (!apiKey) throw new Error('Set MAILSLURP_API_KEY');
const mailslurp = new MailSlurp({ apiKey });
The recipes below run in the test process. Use your language's SDK for the equivalent MFA controller methods.
Pair using an otpauth URL
Read the enrollment URI from the identity provider's setup flow. Some providers expose it in a DOM attribute; others display only a QR image or a manual setup secret. Do not assume every QR image has a readable data attribute.
This example accepts the URI through an environment variable. In an end-to-end test, use the value obtained from that user's enrollment page instead:
const otpAuthUrl = process.env.TEST_USER_OTPAUTH_URL;
if (!otpAuthUrl?.startsWith('otpauth://')) {
throw new Error('Provide the test user enrollment otpauth URL');
}
const device = await mailslurp.mfaController.createTotpDeviceForOtpAuthUrl({
createTotpDeviceOtpAuthUrlOptions: { otpAuthUrl },
});
assert.ok(device.id);
If you have a QR image, use the MFA controller's custom creation options for the supported QR input, or decode it in your test and submit the resulting URI. See the MFA operations in the API reference for request formats.
Pair using a Base32 secret
This is an alternative to the URI recipe:
const base32SecretKey = process.env.TEST_USER_TOTP_SECRET;
if (!base32SecretKey) throw new Error('Provide the test user Base32 enrollment secret');
const secretDevice = await mailslurp.mfaController.createTotpDeviceForBase32SecretKey({
createTotpDeviceBase32SecretKeyOptions: { base32SecretKey },
});
assert.ok(secretDevice.id);
Ensure the code parameters match the identity provider. An enrollment URI can carry parameters that should be preserved instead of assuming a secret alone describes a non-default configuration.
Request and submit a code
After creating device using the URI recipe, request a code just before the application needs it:
const currentCode = await mailslurp.mfaController.getTotpDeviceCode({ id: device.id });
assert.match(currentCode.code, /^\d+$/);
// Fill the application's authenticator code input with currentCode.code.
// Submit the challenge and assert that enrollment or sign-in succeeded.
Use secretDevice.id instead if you paired with a Base32 secret. Request a new code for later challenges; do not cache a code across the suite. Two requests in the same time period can return the same value, and the application's replay policy may reject reuse. If that matters to the test, wait for the next validity period before requesting another code and budget for that explicitly.
Troubleshoot a rejected code
- Verify that the virtual device belongs to the exact user and enrollment being tested.
- Confirm that the secret and parameters match the identity provider, including period, digit count, and algorithm where configured.
- Request the code close to submission so it does not expire while the browser performs other steps.
- Keep the application server's clock synchronized.
- Check whether a previous challenge already consumed the code in the current time period.
- If the user resets MFA, pair a device with the new enrollment secret.
Run the Auth0 examples
Info: Selenium and Playwright walkthroughs. The Auth0 MFA example project includes a Java Selenium test that enrolls a user and signs in again, plus a Playwright enrollment example. The Selenium TOTP tutorial explains the browser steps.
Keep test users and paired device IDs scoped to the suite that owns them. Follow your test-account teardown policy after the scenario; deleting an email inbox does not unenroll the application's MFA device.