Multi-Factor Signing: Templates and Code Samples to Add MFA to Document Approval Flows
tutorialssecuritydeveloper

Multi-Factor Signing: Templates and Code Samples to Add MFA to Document Approval Flows

UUnknown
2026-02-26
11 min read
Advertisement

Ready-to-use MFA templates and code to add TOTP, WebAuthn, SMS (with caveats), and biometrics to e-sign workflows.

Stop slow, risky approvals: add MFA to signing flows today

Account takeover attacks have surged in late 2025 and early 2026, hitting enterprises and small businesses alike. If your document approval and e-signature flows rely on passwords alone, you're exposing contracts, invoices, and approvals to fraud. This guide gives practical, ready-to-use templates and code samples to add MFATOTP, hardware keys (WebAuthn/FIDO2), SMS (with caveats), and device biometrics — into scanning and signing workflows so you can reduce takeover risk without slowing business down.

Why this matters in 2026

Regulators and security teams are demanding stronger authentication for high-value business operations. In January 2026, several high-profile account-takeover waves underscored how effective weak recovery and password-only flows are for attackers.

Recent reporting in early 2026 shows a clear uptick in credential and account takeover attacks affecting billions of users — businesses should treat signing endpoints as critical assets.

Meanwhile, platform authenticators and passkeys (WebAuthn) are rapidly maturing across browsers and mobile OSes. That means you can provide phishing-resistant authentication (hardware keys and platform biometrics) in signing flows with minimal UX friction.

High-level approach (inverted pyramid)

  1. Protect signing sessions — require MFA at time of signing for sensitive workflows.
  2. Use phishing-resistant factors (WebAuthn/biometrics) for the highest-risk approvals.
  3. Use TOTP for broad compatibility and offline capability.
  4. Use SMS only as a fallback — log caveats, apply risk-based gating, and enforce recovery controls.
  5. Record and sign audit trails so MFA events become tamper-evident parts of the approval record.

Practical workflow template: Enforce MFA in a signing flow

Use this step-by-step template as the reference flow for any e-sign or scan-to-sign workflow. All code samples below implement parts of this template.

  1. Start signing session: user clicks "Sign" on document in app.
  2. Check session context: document sensitivity, approver role, risk score (IP, device, velocity).
  3. If risk or sensitivity requires it, prompt for MFA (TOTP / WebAuthn / Biometrics).
  4. Verify MFA server-side and record an MFA event: method, timestamp, result, and assertion details.
  5. Generate a signed audit entry (JWT or server-signed record) and attach it to the final signed document.
  6. Complete signing via your e-sign provider API and store the combined artifact with audit metadata.

Template: Policy snippets for approval workflows

Drop these into your workflow config or policy engine to classify when MFA is required.

{
  "approval_policy": {
    "high_risk": {
      "criteria": ["amount>10000", "sensitivity:contract", "external_counterparty"],
      "mfa_required": true,
      "mfa_methods": ["webauthn","totp"]
    },
    "default": {
      "criteria": [],
      "mfa_required": false,
      "mfa_methods": ["totp","sms"]
    }
  }
}

TOTP (RFC6238) — quick integration

When to use: Good baseline; works offline; widely supported by authenticator apps. Pair with device-bound factors where possible.

Server: generate secret and QR (Node.js / Express using speakeasy)

// npm i express speakeasy qrcode
const express = require('express');
const speakeasy = require('speakeasy');
const QRCode = require('qrcode');
const app = express();

app.get('/mfa/setup', async (req, res) => {
  const userId = req.query.userId; // your authenticated user id
  const secret = speakeasy.generateSecret({ length: 20, name: `AcmeCorp:${userId}` });
  // save secret.base32 into your user record securely

  const otpAuth = secret.otpauth_url;
  const qr = await QRCode.toDataURL(otpAuth);
  res.json({ qr, secret: secret.base32 });
});

app.post('/mfa/verify-totp', express.json(), (req, res) => {
  const { userId, token } = req.body;
  const secret = /* load secret.base32 from DB */ '';
  const verified = speakeasy.totp.verify({ secret, encoding: 'base32', token, window: 1 });
  // log MFA event and proceed with signing if verified
  res.json({ verified });
});

app.listen(3000);

Note: store secrets encrypted at rest and restrict access. Log TOTP verification as part of audit trail.

WebAuthn / Hardware Keys (FIDO2) — phishing-resistant

When to use: High-value approvals where phishing resistance is essential. Supports USB/NFC/Bluetooth hardware keys and platform authenticators (Touch ID / Face ID).

Client: registration (browser)

// client-side: call server to get registration options, then:
const options = await fetch('/webauthn/register-options?userId=123').then(r => r.json());
const cred = await navigator.credentials.create({ publicKey: options });
const attestation = { id: cred.id, rawId: arrayBufferToBase64(cred.rawId), response: { clientDataJSON: arrayBufferToBase64(cred.response.clientDataJSON), attestationObject: arrayBufferToBase64(cred.response.attestationObject) } };
await fetch('/webauthn/register', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({ attestation }) });

Server: verify registration (Node.js using fido2-lib)

// npm i fido2-lib
const { Fido2Lib } = require('fido2-lib');
const f2l = new Fido2Lib({ timeout: 60000, rpId: 'example.com', rpName: 'Example Corp', challengeSize: 64 });

app.post('/webauthn/register', express.json(), async (req, res) => {
  const attestation = req.body.attestation;
  const expected = /* load challenge and user info from session */ {};
  const regResult = await f2l.attestationResult(attestation, expected);
  // store regResult.authnrData.get('credId') and publicKey
  res.json({ ok: true });
});

Server: verify assertion at signing time

app.post('/webauthn/verify-assertion', express.json(), async (req, res) => {
  const assertion = req.body.assertion; // from navigator.credentials.get
  const expected = /* challenge and stored publicKey */ {};
  const authnResult = await f2l.assertionResult(assertion, expected);
  // authnResult includes signCount — useful for detecting cloned keys
  // Log MFA event with authnResult info and attach to audit log
  res.json({ verified: true });
});

Best practice: enforce userVerification: 'required' for biometric platform authenticators to mandate local biometrics during signing.

Device biometrics (platform authenticators)

Device biometrics are implemented via WebAuthn platform authenticators. To require biometrics, set userVerification to 'required' and the browser will demand local PIN/biometrics. This gives strong, phishing-resistant assurance while keeping UX native.

SMS — use with caution

When to use: As a fallback for users who cannot use TOTP or WebAuthn. Do not use SMS as the primary factor for high-value approvals due to SIM swap and interception attacks.

Operational controls if you use SMS:

  • Limit SMS to low-risk flows or as secondary fallback.
  • Require re-enrollment and challenge for number changes.
  • Log SMS MFA events and flag any high-frequency or cross-geography changes.
  • Use carrier checks and fraud-risk scoring where available.

Example: Twilio SMS code send (Node.js)

// npm i twilio
const twilio = require('twilio')(process.env.TWILIO_SID, process.env.TWILIO_TOKEN);
app.post('/mfa/sms/send', express.json(), async (req, res) => {
  const { phone, userId } = req.body;
  const code = Math.floor(100000 + Math.random() * 900000).toString();
  // store hashed code with short TTL and attempt counters
  await twilio.messages.create({ body: `Your code: ${code}`, to: phone, from: process.env.TWILIO_FROM });
  res.json({ sent: true });
});

Remember: treat SMS results as weaker signals and combine with device fingerprinting or risk scoring for higher assurance.

Audit trail: make MFA events tamper-evident

Every MFA event must be recorded and cryptographically bound to the signed document. Use a signed audit record (JWT or CMS/PKCS7) that contains:

  • documentId, userId, method, timestamp
  • assertion details (WebAuthn signature counter, credential ID) for hardware keys
  • verification result and server-signed hash

Example: create a signed audit JWT (Node.js)

// npm i jsonwebtoken
const jwt = require('jsonwebtoken');
function createAuditJWT(auditEvent) {
  // auditEvent: { docId, userId, method, result, details }
  const payload = { ...auditEvent, iat: Math.floor(Date.now() / 1000) };
  return jwt.sign(payload, process.env.AUDIT_JWT_SECRET, { algorithm: 'HS256', expiresIn: '30d' });
}

// store the JWT alongside the signed document and in your centralized audit DB

Putting it together: Signing flow code sketch (Node.js + pseudo e-sign API)

The flow below shows the core pieces: check policy, trigger MFA, verify, generate audit entry, finalize signature with e-sign API.

async function handleSignRequest(req, res) {
  const { userId, documentId } = req.body;
  const policy = await loadPolicyForDocument(documentId);
  const risk = await computeRisk(req); // IP, device, velocity

  if (policy.mfa_required || risk.high) {
    // present MFA choices to user based on allowed methods
    return res.json({ mfaRequired: true, methods: policy.mfa_methods });
  }

  // if no MFA required, proceed (but still record an event)
  const auditJwt = createAuditJWT({ docId: documentId, userId, method: 'none', result: 'proceed' });
  const signedDoc = await eSignApi.sign(documentId, userId, { auditJwt });
  res.json({ signedDoc });
}

// After client verifies MFA (WebAuthn/TOTP), server validates and continues:
async function finalizeAfterMFA(req, res) {
  const { userId, documentId, mfaAssertion } = req.body;
  const verified = await verifyMfaAssertion(userId, mfaAssertion);
  if (!verified) return res.status(401).json({ error: 'MFA failed' });

  const auditJwt = createAuditJWT({ docId: documentId, userId, method: mfaAssertion.method, result: 'verified', details: mfaAssertion.details });
  const signedDoc = await eSignApi.sign(documentId, userId, { auditJwt });
  res.json({ signedDoc });
}

Example UI copy & email template for signer MFA prompt

Use clear, low-friction copy to instruct signers:

Subject: Action required: Verify your identity to sign Contract #12345

Hi Sarah,

To complete signing of Contract #12345, please verify your identity. Choose a method:
• Security key or device biometrics (recommended)
• Authenticator app (TOTP)
• SMS code (fallback)

This helps protect your account and ensures the signed document is secure.

Thanks,
AcmeCorp Security

Recovery, onboarding, and lifecycle

  • Make MFA enrollment part of the user setup for any approver role.
  • Offer multiple registered authenticators per user (e.g., a platform authenticator and a backup TOTP).
  • Enforce re-authentication for changes to phone numbers or registered authenticators; require in-person or admin verification for high-value account recovery.
  • Rotate server keys used to sign audit records regularly, maintain key material in an HSM or cloud KMS, and log key operations.

Security best practices

  • Prefer WebAuthn/passkeys or hardware keys for high-value signatures — they’re phishing-resistant.
  • Store minimal sensitive data from authenticators (store public keys and credential IDs; never raw private keys).
  • Include MFA details in signed audit logs so you can prove who authenticated, how, and when.
  • Use risk-based policies to require stronger factors for remote, new-device, or high-amount approvals.
  • Monitor for MFA failures and recovery events — spikes can indicate active takeover attempts.
  • Enforce rate limiting and CAPTCHA on enrollment and verification endpoints to prevent automated attacks.

Real-world example: accounts and approvals at Acme Logistics (case study)

Acme Logistics adopted WebAuthn for accounts with signing privileges in mid-2025 and rolled it out to all approvers by Q1 2026 after seeing an industry-wide rise in credential attacks. Results in the first 6 months:

  • Phishing-induced account takeover attempts dropped by 78% on signing endpoints.
  • Approval turnaround time improved because signers used platform biometrics instead of waiting for SMS codes.
  • Audit queries during compliance checks took 40% less time because MFA events were embedded in signed artifacts.

Adoption trends we expect to continue through 2026:

  • Passkeys and WebAuthn will become the default for business-critical flows. Major OS and browser vendors in 2025–2026 have expanded passkey UX and migration tools, lowering adoption friction.
  • Regulators will push for stronger authentication in financial and procurement workflows. Expect tighter guidance for signing high-value contracts and payments.
  • Risk-based MFA will mature with ML-backed signals. Combining device telemetry, behavior, and MFA strength will be a standard control.

Troubleshooting & FAQs

Q: What if a user loses their hardware key?

A: Enforce backup authenticators at enrollment (TOTP + secondary device) and an administrator-controlled recovery workflow with out-of-band verification. For high-value accounts, require in-person or notarized recovery.

Q: Can I force biometric-only on mobile?

A: Yes — use WebAuthn with userVerification:'required' and rely on platform authenticators. Provide a fallback for users without compatible devices.

Q: How should I present MFA evidence during audits?

A: Provide the signed audit JWT or signed audit bundle that includes MFA method, credential IDs, assertion counters, and signature over the document hash. This proves the identity transaction and can be verified independently.

Checklist before you roll out MFA for signing

  • Map all signing flows and classify their risk levels.
  • Update workflow templates to require MFA for high-risk classes.
  • Implement WebAuthn + TOTP and keep SMS as a documented fallback only.
  • Integrate MFA events with your e-sign provider and store signed audit records.
  • Train approvers with clear UX copy and a simple onboarding path for passkeys.
  • Monitor for anomalies and prepare an incident response plan specific to MFA compromises.

Developer resources & reusable code snippets

  • TOTP: speakeasy + QR generation — see earlier example
  • WebAuthn: fido2-lib (Node) and navigator.credentials API — samples above
  • SMS: Twilio example above; always hash codes and enforce TTLs
  • Audit signing: use JWT or PKCS7; store signatures in KMS/HSM

Final recommendations

In 2026, protecting signing workflows is not optional. Implement a layered approach: prefer WebAuthn/passkeys for critical approvals, use TOTP broadly, and reserve SMS as fallback — all while recording cryptographically-signed audit evidence. Combine MFA with risk-based gating and lifecycle controls to stop account takeovers cold and keep your contracts and approvals legally and forensically sound.

Call to action

Ready to add phishing-resistant MFA to your signing workflows? Get our downloadable repository of templates, Node/Python samples, and audit-signing utilities — or request a sandbox demo to test WebAuthn and TOTP in your environment. Contact our team to start a pilot and protect your approvals from account takeover today.

Advertisement

Related Topics

#tutorials#security#developer
U

Unknown

Contributor

Senior editor and content strategist. Writing about technology, design, and the future of digital media. Follow along for deep dives into the industry's moving parts.

Advertisement
2026-02-26T03:42:55.264Z