Skip to content
nviaOTP

Risk-Based Step-Up & Account Recovery · Chapter 3 of 5

Step-up UX that doesn't strand users mid-flow

Interrupt at the right moment, preserve the user’s work, offer the strongest factor they actually have, and always leave a safe way forward.

5 min read

A well-designed policy can still fail at the moment it touches a person. The member is halfway through a transfer, the policy says “step up”, and what happens next decides whether the control protects them or drives them to call support, abandon the payment or disable the feature. This chapter is about that moment. The goal is simple to state: the user should never lose work, never face a factor they cannot complete, and never be left without a safe next step.

At Tidewater Mutual, Marisol pairs with product designer Ines Varga-Mbeki. Their example member is Dorian Achterberg, who is paying a contractor €1,850 from a laptop he has not used for banking before — an illustrative example. The policy places him in the unfamiliar band for a value-moving action: step up with an OTP or stronger.

Interrupt late, not early

There are two places to ask for more: at the start of a flow or at the point of commitment. Asking at the start (“Verify it’s you before opening Payments”) feels safe but is wasteful — many users browse without acting — and it detaches the challenge from the action, so the prompt cannot describe what is being approved. Asking at commitment (“Confirm payment of €1,850 to Kestrel Joinery”) lets the prompt show the details, and only users who actually commit pay the cost. Tidewater asks at commitment for value and control actions, and only re-checks earlier when the session itself falls into the hostile band.

The draft is stored server-side before the challenge, and the proof is bound to that draft. The user never retypes the payment.

Preserve the work

The most common stranding failure is a redirect. The API returns 401, the front end bounces the user to a separate verification page, and when they come back the form is empty. Dorian has to re-enter the IBAN and the amount, and one time in ten he gives up. The fix is structural: the server stores the draft action before issuing the challenge, the challenge references the draft, and the client retries the original request with a proof. Front-end state can be lost to a reload or a phone call; a server-side draft with a short expiry survives both.

// 401 body for a step-up: everything the client needs to continue in place.
{
  "error": "step_up_required",
  "challenge_id": "c91",
  "draft_id": "7f2c",
  "accepted_factors": ["passkey", "app_approval", "totp", "sms_otp"],
  "display": { "action": "Pay Kestrel Joinery", "amount": "€1,850.00" },
  "expires_in": 300
}
A self-describing step-up response lets any client — web, iOS, Android — render the challenge without a redirect.

Offer what the user actually has

accepted_factors lists what policy allows; the client should intersect it with what the user has enrolled and what the current device can do. Dorian has a passkey synced to his phone and an SMS number. On the unfamiliar laptop the browser can still use the phone through a cross-device passkey flow, so the UI offers that first, with “Text me a code instead” as a visible secondary option. It does not show TOTP, because he never enrolled an app, and it does not show a passkey button that would fail on a device without one.

Ordering matters. Lead with the strongest factor that is likely to succeed, and make the alternatives one tap away rather than hidden behind “Having trouble?”. A user who hits a dead end with no visible alternative is stranded even if alternatives technically exist. Conversely, do not offer factors weaker than policy allows: if the action is a control action, an SMS option should not appear at all, rather than appearing and then failing.

Attacker view

How step-up strands people

  • Redirect to a verification page that drops the form.
  • A factor the device cannot complete, with no alternative shown.
  • A generic “Verify it’s you” with no action details.
  • A timeout that discards the draft and starts over.

Defender view

How it keeps them moving

  • Server-side draft and in-place challenge.
  • Strongest viable factor first, others one tap away.
  • Prompt names payee and amount.
  • Expiry long enough to fetch a phone; clear restart that restores the draft.

Failure is part of the flow

Factors fail. The code arrives late, the passkey sheet is dismissed, the phone is in another room. Each failure should leave the user in the challenge, not at the start. Count attempts against the challenge, not against the account, so a fumbled passkey prompt does not lock Dorian out of his bank. When the attempt budget for the challenge is spent, discard that challenge but keep the draft for a few minutes and explain the next step plainly: “We couldn’t confirm this payment. Try again from your phone, or call us on the number on your card.”

Error messages need the same care as on the main login. Do not reveal which signal triggered the step-up (“Your IP is from a hosting provider”) — that is a free oracle for attackers tuning their infrastructure. A neutral “For your security, we need to confirm this payment” is enough. Record the actual reason codes in the decision log where support can see them.

Accessibility and context

Step-up prompts appear at stressful moments, often on small screens. Keep them operable by keyboard and screen readers, announce the challenge with an appropriate live region, support platform autofill for one-time codes, and avoid time limits shorter than a few minutes. For members who cannot use a given factor — no smartphone, a visual impairment that makes code entry hard — the alternatives in the list are not a nicety, they are the only path. The next chapter covers what happens when every path is lost: recovery.

0/3 correct · 0 answered

  1. 1. Why ask for step-up at the point of commitment rather than at the start of a flow?

  2. 2. What prevents the user from losing a half-completed payment during step-up?

  3. 3. Should the step-up message tell the user which signal triggered it?

Discussion

Comments are public and shared with every reader. No account needed — just a display name. Comments reported by three readers are hidden automatically.

15 more characters needed

Loading comments…