DEV Community

MortimerNilsson7694
MortimerNilsson7694

Posted on

Step-Up Authentication in Marketplaces: Where to Apply Fresh Payout Verification

Choice Fit Main cost
Gate sensitive actions after ordinary login Payout edits, account recovery, and other consequential changes Track recent verification on the server
Challenge at every login A session itself grants unusually broad powers More friction on routine browsing
Trust the existing session alone Low-impact actions A stolen session retains its full authority

Short answer: for a marketplace moving phone one-time-code login off a managed provider, keep routine sign-in separate from a fresh check before changing a seller's payout destination. Step-up authentication asks for additional evidence when an action needs more assurance than the current session provides. It does not mean texting another code for every page view. Start with a server-side gate on the payout change, then extend the same policy to other high-impact actions only when their risk warrants it.

Where should step-up authentication apply in a marketplace?

The useful boundary is the action, not the screen. A seller browsing orders and a seller replacing a payout destination may use the same session, but the latter has a different consequence if that session is stolen. OWASP's Authentication Cheat Sheet recommends reauthentication after risk events and before critical actions, including changing payment details. That makes a payout update a defensible first gate. A password reset, recovery-method change, or removal of another authenticator can also alter who controls the account. Treat each as its own policy decision rather than copying a challenge into every settings form.

Phone one-time codes complicate the decision. A code sent to the same phone number used for login may establish freshness, but it does not automatically add an independent factor. NIST SP 800-63B describes restrictions and risks for out-of-band authentication over the public switched telephone network. If the threat includes control of that number, sending another SMS to it does not resolve the threat. Stronger methods need a separate enrollment and recovery plan. Do not describe a repeated SMS challenge as phishing-resistant MFA.

Fresh isn't independent.

What changes when login leaves a managed provider?

The first criterion is who owns the decision. During migration, old and new login paths may both create valid app sessions. The payout endpoint must apply one authorization rule to both; otherwise the route by which a seller signed in quietly determines whether a payout change needs verification. Put the decision at the server action boundary, and keep the session's sign-in time distinct from the time and method of the latest qualifying verification. A client-side modal is presentation, not enforcement.

The second criterion is what counts as fresh enough. Set a short, explicit window for the action, keyed to an authenticated account and session, and require a new check outside that window. The exact duration is a risk choice, not a universal standard. For a payout edit, bind approval to the pending destination change or require a new check if the proposed destination changes. Otherwise someone can verify a benign change and submit a different destination while the assurance window remains open. OWASP's Transaction Authorization Cheat Sheet discusses binding authorization to the transaction details; the same principle matters here. Suppose a seller opens a payout form with one destination, completes verification, then edits the destination before submitting: a check tied only to the session cannot distinguish the approved proposal from the final one. That is the failure to test, including when the seller has two tabs open and the requests arrive out of order.

This is also a DX test. How many pieces of glue must an SDK or CLI caller assemble to learn that a fresh check is required? Return a stable, machine-readable challenge state from the protected operation, alongside an opaque pending-change identifier. Record the outcome without logging the code or full payout details. Measure challenge starts, successful completions, expired attempts, and protected-action denials by login path during rollout. No invented conversion benchmark can substitute for those measurements.

A small server-side gate

The example deliberately leaves code delivery and verification behind an interface. The important part is that a completed challenge applies only to the exact pending change, after the server verifies the authenticated session again. A production implementation also needs durable pending-change storage, attempt limits, expiration, replay protection, and atomic consumption of approvals.

type Session = { id: string; accountId: string };
type PendingPayout = {
  id: string;
  accountId: string;
  sessionId: string;
  destinationFingerprint: string;
  expiresAt: number;
};

async function approvePayoutChange(
  session: Session,
  pendingId: string,
  code: string,
): Promise<void> {
  const pending: PendingPayout | null = await loadPending(pendingId);
  if (!pending || pending.accountId !== session.accountId ||
      pending.sessionId !== session.id || pending.expiresAt <= Date.now()) {
    throw new Error("Verification expired or unavailable");
  }

  const verified = await verifyBoundCode({
    accountId: session.accountId,
    sessionId: session.id,
    pendingId,
    code,
  });
  if (!verified) throw new Error("Verification failed");

  await commitPayoutChangeAndConsumeApproval(pending);
}
Enter fullscreen mode Exit fullscreen mode

The fingerprint must be computed by the server from the proposed destination, then checked again inside the final transaction. A pending ID by itself is not proof.

Test the replay.

Rate-limit verification attempts and avoid revealing whether a phone number or account exists in error messages. For tests, try a valid code against a different pending change, a different session, and an expired request; all three must fail. Also test two concurrent submissions so only one consumes the approval. Keep the failure response useful to the legitimate client without turning it into an account-enumeration signal.

When is the runner-up better?

The limitation of an action-specific gate is coverage: every consequential write needs the same server-side enforcement, and a missed endpoint leaves a gap. Challenging at sign-in can make sense when nearly every authenticated operation is consequential, or when the app cannot yet enforce policy consistently across all protected actions. It is a blunt instrument for a marketplace where buyers mostly browse and sellers occasionally edit payouts. A successful login challenge also ages: a long-lived session still needs a fresh check for a later high-impact edit.

During a staged migration, test the same protected action through both old and new session issuers before shifting traffic. Keep rollback scoped to login issuance, not to the payout gate. Track denied actions and challenge completion separately so a login migration does not disguise a broken authorization boundary. The least complex policy is the one that keeps the high-risk operation protected even when the sign-in path changes.

Further reading

References

Top comments (0)