A long callsign-transfer form is in flight when the step-up challenge fires: how do you resume it without re-executing the request?
answer
- park it, do not hand it back
- an opaque ticket, not the payload
- bound to session and attempt
- consume before you execute
- short time-to-live, then it dies
basics
~20 sPark the parsed command server-side as a single-use suspension record bound to the session, the pending challenge attempt and a short lifetime, and redirect with an opaque ticket only. On return, consume the suspension atomically, re-validate, then execute once.
solid answer
~40 sDo not hand the half-finished request back to the browser. The server parks the **parsed, validated command** it already received as a suspension record — bound to the session, to the pending challenge attempt, to the exact operation and payload, and to a short time-to-live — and redirects into the challenge carrying only an opaque suspension identifier. When the challenge succeeds, the resume path looks the suspension up, checks the binding and the elevation, atomically flips it from parked to consumed, re-checks authorisation, re-validates the payload against current state, and executes once inside that same transaction. Abandoning the challenge simply lets the suspension expire unexecuted. If a second tab resumes the same suspension, the compare-and-set fails and that tab is told the transfer has already been applied rather than performing a second one.
code
pseudocode · 30 lines# on the first, unelevated submission
function submit_transfer(session, form):
command = parse_and_validate(form) # server holds the payload
s = store.park(
session_id = session.id,
operation = "callsign.transfer",
command = command,
attempt_id = start_challenge(session, "callsign.transfer"),
state = "parked",
expires_at = now() + 10.minutes)
return redirect_to_challenge(suspension = s.id) # opaque id only
# on return from the challenge
function resume(session, suspension_id):
s = store.find_suspension(suspension_id)
if s is null or s.session_id != session.id or now() > s.expires_at:
return gone()
if not attempt_satisfied(s.attempt_id) or not elevation_covers(session, s):
return demand_fresh_challenge(session, s.operation)
begin_transaction()
if not store.compare_and_set(s, "parked", "consumed"):
rollback(); return already_applied(s) # the second tab lands here
if not may_perform(session.holder, s.operation, s.command):
rollback(); return deny_not_allowed()
if not still_valid(s.command): # the world moved
rollback(); return stale(s)
perform(s.command)
commit()
return ok()go deeper
Recall the shape: the server keeps the part-filled request and gives the browser only a meaningless ticket, then matches the ticket back to that one request after the challenge passes.
Explain what the suspension is bound to and why — session, challenge attempt, exact payload, short expiry — and why the payload must not travel with the browser through the challenge.
Demonstrate the ordering. Consume before you execute, inside one transaction, and say what the two-tab and crash-retry cases do under each ordering. Then say why the parked payload is re-validated rather than trusted.
Weigh whether this mechanism is worth building at all against simply refusing long privileged forms mid-flight. Name the operations where losing a submission is expensive enough to justify a second stateful record, and who owns its expiry and storage.
## The request that is already in flight The callsign-transfer form on an amateur-radio licensing portal is long: the receiving holder, the licence class, the effective date, a declaration. The holder fills it in, presses submit, and only then does the server discover that this operation demands a fresh challenge. Three responses present themselves and all three ship regularly — throw the submission away and make the holder retype it, run the transfer first and challenge afterwards, or hand the half-finished request back to the browser to carry through the challenge and post again. The first is safe but rude. The second is the outright bug. The third is the subtle one, and it is the one that looks like a solution. ## Park it on the server, hand back a ticket The arriving request is parked as a **suspension record** in the server's own storage: the parsed and validated command, not the raw form bytes, together with the session it came from, the operation it is, and a short expiry. The response to the browser is a redirect into the challenge carrying nothing but an **opaque suspension identifier** — a ticket stub, not the form. Why not give the payload back to the browser to bring along? Because then the payload is under the control of the party being challenged. The holder can pass the challenge for a transfer to one recipient and post back a transfer to a different one; the challenge then authorised something the server never saw. Signing the payload makes tampering detectable, but it does not stop the holder re-presenting an earlier signed payload, or substituting another one the same holder legitimately obtained. What the challenge is supposed to authorise is a specific operation the server has already read, and the only way to be sure of that is for the server to be the one holding it. ## What the suspension is bound to - **The session**, so no other session can resume it. - **The pending challenge attempt**, so a challenge passed for some other purpose in another tab does not release this one. - **The operation and its exact payload**, so what commits is what was challenged for. - **A short time-to-live**, measured from the moment of parking, and deliberately shorter than any session clock. ## Resuming exactly once The resume path is the whole design, and its order matters: 1. Look up the suspension by its identifier and verify the binding: same session, and the attempt it names was the one satisfied. 2. Check that the elevation the challenge produced actually covers this operation and this callsign. 3. **Atomically transition the suspension from parked to consumed**, and stop if it was already consumed. 4. Re-check ordinary authorisation, and re-validate the parked payload against current state. 5. Execute, once, and commit the consumption in the same transaction as the write. Step three before step five, inside one transaction, is what makes the resume idempotent. Consume-then-execute means a duplicate arrival finds the record already consumed and does nothing; execute-then-consume leaves a window in which a crash, a retry or a second tab runs the transfer twice. Step four is not ceremony. The parked command may be minutes old, and the world moved: the receiving holder's licence may have lapsed, the callsign may already have been transferred by another path, the submitting holder's own rights may have been withdrawn. A suspension is a paused request, never a pre-approved one, and re-validating at resume is what keeps that true. ## Abandonment and the second tab Abandonment is the easy case precisely because the design made it easy. The holder closes the tab, the challenge is never completed, nothing is consumed, and the suspension expires at its own time-to-live having executed nothing. There is no cleanup path to get wrong because a parked record carries no authority: it is a copy of a request, and an unresumed request is simply a request that never happened. Two tabs is the case that repays the atomic consume. Both tabs hold the same suspension identifier; one challenge completes; both resume. The first compare-and-set wins and commits the transfer. The second finds the state already consumed and must answer honestly — the transfer has been applied, here is the result — rather than starting a second one or reporting a failure that invites the holder to try again. That answer is a user-facing decision, but it rests entirely on the record having a state rather than merely existing. ## What the browser is trusted with Only the opaque identifier, and only as a pointer. It is not capability enough on its own: without the bound session and the satisfied attempt it resolves to nothing usable, so an identifier leaking into a shared screen, a history entry or a referrer does not hand anyone a transfer.
- Why re-check authorisation and re-validate the payload at resume, when both were checked when the request first arrived?Because minutes passed inside the challenge. The receiving holder's licence may have lapsed, the callsign may already have moved, the submitting holder's own rights may have been withdrawn. A suspension is a paused request, not a pre-approved one; treating it as approved turns the challenge into a way of freezing a decision made against stale state.
- The same suspension is resumed from two tabs after one challenge. What does the second tab get?The first resume wins the atomic transition from parked to consumed and commits the transfer. The second finds the record already consumed, performs nothing, and is told the transfer has already been applied, with its result. Reporting a generic failure instead is the trap: it invites the holder to retry, and retries are exactly what the single-use state exists to absorb.
- Is the opaque suspension identifier sensitive enough to keep out of logs and referrers?Treat it as low-value but not public. On its own it authorises nothing: resuming demands the bound session and a satisfied challenge attempt, so a leaked identifier is not a transfer. Keeping it out of URLs that travel onward is still worth doing, because it names a pending privileged operation and that is information about the holder.
A licensing counter clerk who cannot accept your application until you fetch photo identification does not hand the part-filled form back across the counter. The office keeps the form and gives you a numbered stub. You return with the identification, the stub is matched to that one form, and the stub is torn as it is used: it will never produce that form a second time, and if you never come back the form is shredded at the end of the day having done nothing.
saying these in an interview costs you the question
- Handing the half-finished request back through the browser across the challenge.
- Executing the privileged write first and demanding the challenge afterwards.
- Resuming without marking the suspension consumed, so a retry runs it twice.
- Believing the only safe option is discarding the submission and retyping the form.
- Treating the parked payload as pre-approved and skipping re-validation at resume.
- Giving the suspension no expiry, so parked requests accumulate indefinitely.