ThisCalculator

Everyday

One-Time API Tokens: Replay Attack Defense & Trade-Offs

Updated · Standard formula, shown below

One-Time API Tokens: Replay Attack Defense & Trade-Offs

Yes, one-time API tokens can stop replay attacks, but only if the server strictly enforces single-use semantics and the token is bound to the intended request context. This article explains how one-time tokens work, the design trade-offs they introduce (especially around retries and distributed systems), and practical strategies for handling retries without breaking security.

Background: What Are Replay Attacks?

A replay attack occurs when an attacker intercepts a valid API request (often a token, signature, or entire message) and re-sends it to the server, hoping the server will process it again as if it were a fresh, legitimate request. This can lead to duplicate orders, unauthorized fund transfers, or repeated state changes. Traditional defenses include timestamps (rejecting old requests), nonces (unique numbers that the server remembers), and TLS (which protects in transit but not at the application layer). One-time tokens are a stronger variant: they are valid for exactly one use, after which the server invalidates them permanently.

How One-Time Tokens Work

A one-time token is typically a random string or a signed value generated by the server and delivered to the client (e.g., in a response header, a cookie, or a JSON field). The client includes it in the next request. The server stores the token in a fast datastore (like Redis) with a short expiry, and on receiving a request, it atomically deletes the token. If the deletion succeeds, the request is processed; if the token is already gone, the request is rejected as a replay.

This atomic check-and-delete is the core security property. Without atomicity, two concurrent requests could both read the token before either deletes it, allowing a race-condition replay. In practice, this is implemented using:

  • Redis DEL command (returns 1 if deleted, 0 if not)
  • SQL DELETE ... WHERE token = ? AND expires_at > NOW() and checking affected rows
  • In-memory single-threaded stores for single-instance deployments

Design Trade-Offs: Idempotency vs. Strict Single-Use

The main trade-off is between strict single-use (which rejects any duplicate) and idempotency (which allows duplicates but makes them harmless). A one-time token is strict: it rejects the second request outright. This is ideal for actions that must happen exactly once, like payment submission or account deletion. However, it creates a problem for retries: if the client sends the request, the server processes it and deletes the token, but the response is lost on the network, the client will retry with the same token and get a rejection. The client cannot know whether the original request succeeded or failed.

In contrast, an idempotency key (a different mechanism) lets the server store the response of the first request and return it again on retry, so the client can safely retry without side effects. One-time tokens are often combined with idempotency keys: the token proves freshness, and the key ensures retries return the same result.

Retry Handling Strategies

When using one-time tokens, you need a strategy for retries that doesn’t break the user experience. Common approaches:

  • Client-side retry with new token: If a retry is needed, the client fetches a fresh one-time token from the server before retrying. This works but adds latency and requires the client to distinguish between “not processed” (safe to retry with new token) and “processed but response lost” (must not retry).
  • Server-side idempotency cache: The server stores the response keyed by the token (or a separate idempotency key) for a short period. On a retry with the same token, it returns the cached response instead of rejecting. This requires careful cleanup to avoid unbounded storage.
  • Client-side correlation ID: The client generates a unique correlation ID for the operation and includes it in the request. The server can detect duplicates by this ID and return the original response, while the one-time token still prevents replay of the entire request.

In practice, most APIs use a combination: one-time token for replay protection, plus an idempotency key for safe retries. For example, Stripe’s API uses idempotency keys, not one-time tokens, but the principle is similar.

Choosing Between One-Time Tokens and Alternatives

One-time tokens are not always the best choice. Consider these alternatives:

  • Timestamp + nonce: The server rejects requests older than a few minutes and remembers nonces for a sliding window. This allows multiple uses of the same token within the window, which is fine for read-only or idempotent operations.
  • HMAC signatures: The client signs the request with a secret key. The server verifies the signature and optionally checks a nonce. This provides integrity and replay protection without storing tokens.
  • JWT with short expiry: A JWT with a 5-minute expiry limits replay window but doesn’t prevent replay within that window. One-time tokens are stricter.

Choose one-time tokens when the operation is non-idempotent and high-value (e.g., transferring money, changing a password). Use timestamps or signatures for lower-stakes operations where a small replay window is acceptable.

Common Pitfalls and Misconceptions

A frequent misconception is that a one-time token alone protects against all replay attacks. In reality, if the token is not bound to the request (e.g., it’s a generic token that can be used for any action), an attacker could replay a captured token in a different context. Always bind the token to the specific operation, user, and expiry.

Another pitfall is relying on client-side deletion of the token. The server must be the source of truth. If the client deletes the token after sending, but the request fails to reach the server, the client cannot retry with the same token because it’s gone. That’s why server-side idempotency is crucial.

Also, token storage must be atomic. In a multi-instance deployment, use a shared store like Redis with atomic operations. If you use a database, ensure the delete is atomic and check the affected rows. A naive read-then-delete in application code can lead to race conditions.

Costs and Performance Considerations

One-time tokens add overhead: you need to generate, store, and delete tokens. For high-throughput APIs, the storage and lookup can become a bottleneck. Use a fast in-memory store with TTL to automatically expire unused tokens. The cost is proportional to the number of tokens issued, so issue them only when necessary (e.g., for state-changing requests, not for GET requests).

Also consider that one-time tokens increase the number of round trips: the client must first request a token, then use it. This adds latency. To mitigate, you can batch token issuance or use a single token for multiple operations within a short window (but that weakens replay protection).

Frequently Asked Questions

Can a one-time token be reused if the attacker replays it before the server deletes it?

No, if the server atomically deletes the token on first use, any concurrent or subsequent request with the same token will fail because the token no longer exists. However, if the server processes the first request but doesn’t delete the token (e.g., due to a bug), replay is possible. Always test for atomicity.

What happens if the client retries with the same one-time token after a network timeout?

The server will likely reject the retry because the token is already used. To handle this gracefully, use an idempotency key so the server can return the original response instead of an error. Alternatively, the client can request a new token and retry, but then the operation might execute twice if the first request actually succeeded.

Are one-time tokens the same as nonces?

Not exactly. A nonce is a unique number used once, often combined with a timestamp. One-time tokens are a specific type of nonce that the server stores and deletes. Nonces can be used in a sliding window (e.g., accept any nonce seen in the last 5 minutes), while one-time tokens are strictly single-use.

How do I implement one-time tokens in a distributed system?

Use a shared atomic store like Redis with the DEL command. For example, in Redis: DEL token returns 1 if the token existed and was deleted, 0 otherwise. This ensures only one instance can successfully use the token. Ensure the token has a TTL to avoid memory leaks.

Can one-time tokens be used for GET requests?

Technically yes, but it’s usually unnecessary. GET requests are supposed to be idempotent and safe, so replaying them shouldn’t cause harm. If you need to prevent replay of a sensitive GET (e.g., a one-time download link), a one-time token is appropriate.

What is the difference between a one-time token and a CSRF token?

A CSRF token is a per-session secret that prevents cross-site request forgery; it is not single-use and is typically validated for every request. A one-time token is single-use and primarily prevents replay attacks. They serve different purposes and can be used together.

Related

Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.