A password-reset link may be labelled “single use” while still being usable twice. The problem is often not token randomness or expiration. It is a race between two requests that both verify the token before either request marks it as used.

That matters because temporary tokens frequently authorize sensitive actions: resetting a password, verifying an email address, accepting an invitation, or completing account recovery. If the application promises one-time use, concurrent requests should not be able to turn that promise into two successful authorizations.

The useful mental model is: using a one-time token is a state transition, not just a token check. The system must change the token from usable to consumed exactly once, and competing requests must agree on which request won. This article explains how to design that transition, where it belongs in the workflow, and how to test the result.

A valid token and a consumable token are different questions

Suppose a password-reset service stores a record like this:

reset token
  user: 42
  expires: 13:15
  used: false

When a request presents the token at 13:00, the service may check that the token exists, has not expired, and has not been used. Those checks answer an important question:

Is this token currently eligible for use?

They do not by themselves guarantee that this request is the only request allowed to use it.

A naive flow might be:

1. read token
2. verify used == false
3. perform sensitive action
4. set used = true

With one request at a time, this appears to work. With two requests close together, both can read used = false before either writes the new state:

request A                 request B
---------                 ---------
read used=false
                          read used=false
accept token
                          accept token
mark used=true
                          mark used=true

Both requests passed the security decision. Setting the flag twice does not repair that decision afterward.

This is a race condition: correctness depends on the timing of operations that can overlap. For a one-time token, the security property should not depend on one request happening to finish before another begins.

State the threat model

Atomic token consumption reduces replay risk when the same valid temporary credential reaches the application more than once before it is invalidated. The duplicate use may be deliberate, or it may come from retries, double submissions, multiple application instances, or other concurrency.

The control assumes the application can coordinate token state through a system that provides an atomic conditional operation, usually the authoritative data store. Atomic means competing operations cannot observe and update the relevant state as if each were the only operation.

This control does not stop an attacker who obtains an unused bearer token and uses it before the legitimate holder. It does not make predictable tokens unpredictable, extend transport security, or decide whether the person receiving a recovery message should have authority over the account. Token generation, delivery, lifetime, storage, and purpose binding remain separate security decisions.

Single-use consumption limits what can happen after one successful use. It does not establish who should win the first use.

Make consumption part of the authorization decision

A stronger flow combines the eligibility check with the state change:

consume token if:
    token matches
    AND used == false
    AND expires_at > now

if exactly one record changed:
    continue
else:
    reject

The important part is not this particular syntax. The important part is that the data store decides eligibility and consumption as one indivisible operation.

In SQL-like pseudocode, the idea could look like this:

UPDATE reset_tokens
SET used_at = :now
WHERE token_digest = :digest
  AND used_at IS NULL
  AND expires_at > :now;

The application then checks the number of rows affected. If one row changed, this request acquired the right to continue. If zero rows changed, the token was missing, expired, already consumed, or otherwise ineligible.

This is a simplified teaching example. Production code should use the database driver’s parameter binding, transaction behavior, error handling, and affected-row semantics correctly. The token should also be represented and stored according to the application’s token design; the example deliberately avoids prescribing a particular digest construction.

The security improvement comes from the conditional update. Two requests can race to execute it, but only the request that changes the record from unused to used should observe a successful transition. The other request sees that its condition no longer matches.

Consume before granting the sensitive capability

Atomic consumption still leaves an ordering decision: should the token be consumed before or after the sensitive action?

For a one-time authorization token, consuming it first usually gives the clearer security boundary:

validate and consume token
          |
          v
perform authorized action

Once consumption succeeds, later requests cannot acquire the same authorization.

The trade-off is failure recovery. Imagine that a reset token is consumed successfully, but the password update then fails because the database is unavailable. The user may have a consumed token without a completed reset.

When the token record and the protected state can participate in the same reliable transaction, the application may be able to make the transition and protected update commit together. For example, a transaction can conditionally consume a reset token and update the corresponding password record, then commit both or neither.

That is not always possible. The protected action may involve another service, an external system, or an asynchronous workflow. In that case, do not restore the token to “unused” casually after a downstream error. Reopening an authorization credential can create confusing races and make replay behavior harder to reason about. A safer recovery design may issue a new token after the application confirms that the original operation did not complete.

The exact recovery path depends on the action, but the invariant should stay simple: one token grants the sensitive capability at most once.

Do not confuse atomicity with a process-local lock

A mutex inside one application process can serialize requests handled by that process. It may be enough for a genuinely single-process system whose security assumptions guarantee that all token use passes through that lock.

Many production systems do not have that property. They run multiple processes, containers, workers, or servers. A lock in instance A does not automatically stop instance B from accepting the same token.

The coordination point should therefore match the trust boundary. If the database is authoritative for whether the token is unused, enforce the single-use transition there. If another strongly consistent service owns token state, enforce it there instead.

Avoid designs that perform an unlocked read from the authoritative store and then rely on application memory to remember that a token is being consumed. The security decision and authoritative state can drift apart during concurrency, restarts, or failover.

Expiration does not provide single-use semantics

Expiration and consumption solve different problems.

An expiration time limits how long an unused token remains eligible. Single-use state limits how many successful uses the token may authorize during that period.

A token that expires in ten minutes can still be accepted twice within the first second if consumption races. Conversely, an atomically consumed token that never expires cannot be replayed after its first successful use, but an unused copy could remain valuable indefinitely.

For temporary authentication and recovery credentials, both controls are usually appropriate: a bounded lifetime reduces the window in which an unused token has value, while atomic consumption closes the credential after its successful use.

Decide what happens when several tokens exist

Applications also need an explicit policy for multiple outstanding tokens for the same purpose.

Suppose a user requests password recovery three times and receives three different links. Atomic consumption guarantees that each individual token can be used at most once. It does not answer whether using one token should invalidate the other two.

There are two valid models:

per-token policy
A, B, and C are independently usable until each expires or is consumed

per-operation policy
using A completes the reset and invalidates B and C for that reset purpose

The second model often matches user expectations for password recovery: once the password has been reset, older outstanding reset credentials should not remain useful. One way to model this is with a recovery generation, request state, or other account-level condition that the consumption operation also checks.

The important design step is to choose the policy deliberately. Marking token A as used says nothing about token B unless the data model and authorization condition make that relationship explicit.

Keep the error surface simple

A client usually does not need to know whether a rejected token was expired, already used, unknown, or invalidated by a newer recovery action. Detailed distinctions can expose internal state without helping the legitimate user complete the task.

A simple response such as “This link is no longer valid. Request a new one.” often gives the user the action they need while keeping token-state details inside server-side logs and metrics.

Operational visibility still matters. Record enough structured information to distinguish expiration, successful consumption, attempted reuse, and system errors without logging the bearer token itself. This helps developers verify the lifecycle and investigate unexpected retry or replay patterns without creating another copy of the credential in logs.

Test the property under concurrency

A normal test that submits the same token twice in sequence is useful but incomplete:

first use  -> success
second use -> rejected

That test proves the stored state changes eventually. It does not prove that the transition is atomic.

Add a concurrency test that sends multiple valid uses close enough to overlap. Under the intended single-use model, the observable result should be one successful acquisition of the token’s authority and all competing acquisitions rejected.

Also test boundary conditions:

  • a token immediately before and after expiration;
  • a token already consumed before the request starts;
  • two application instances using the same token concurrently;
  • a database or downstream failure after consumption;
  • several outstanding tokens when one completes the protected operation.

The expected outcome should follow the documented token policy, not accidental timing. Run these tests against the same class of data store and transaction behavior used in production, because an in-memory test double may not reproduce the relevant concurrency semantics.

Common designs that look single-use but are not

Deleting the token after performing the action has the same fundamental race as setting used = true afterward. Two requests may both authorize before either deletion happens.

Reading the token and then deleting it in separate statements can also race unless the surrounding transaction and locking semantics make the read-and-consume decision exclusive under the database behavior you actually use.

A signed token is not automatically single use either. A valid signature proves that the token was issued by a holder of the signing key and that the signed content has not been altered. A self-contained signed token does not remember that it was previously presented. If the application requires one-time semantics, it needs state or another mechanism that can represent revocation or consumption.

Finally, a frontend flag such as disabling a submit button only reduces accidental duplicate clicks in that interface. It is a usability measure, not an authoritative security control. Requests can arrive through retries, other clients, or parallel servers.

Use the simplest control that preserves the invariant

Not every temporary value needs single-use semantics. A short-lived identifier that grants no authority may only need expiration. A harmless idempotent operation may tolerate retries by design.

Use atomic consumption when possession of a token grants a capability that should disappear after one successful use. Password-reset credentials, account-recovery credentials, invitation acceptance tokens, and similar one-time authorization links are common examples.

For higher-impact workflows, combine atomic consumption with the controls that address different failure modes: strong random token generation, secure delivery, appropriate expiration, purpose and account binding, careful token storage, rate controls where guessing is relevant, and invalidation rules for related credentials. None of those substitutes for atomic consumption, and atomic consumption does not substitute for them.

Conclusion

“Single use” is not a property created by adding a used column. It is a concurrency guarantee created by how the application changes authoritative state.

Treat token use as an atomic state transition: eligible to consumed. Let exactly one request acquire the token’s authority, reject competing acquisitions, define how failures and multiple outstanding tokens behave, and test the invariant with concurrent requests.

That design turns one-time use from an assumption about request timing into a property the system can enforce.