Skip to main content

Replay protection

Concept

Even a perfectly request-bound passport could be captured and re-sent within its short lifetime. Replay protection makes each accepted passport usable exactly once: the passport's jti is consumed atomically on the first accepted presentation, and any repeat is denied with jti_replay.

The key word is atomic. A naive "check if seen, then mark seen" has a race: two concurrent duplicates can both pass the check before either marks. TrustPlane uses a single consume-on-accept operation so that out of N concurrent duplicates, exactly one succeeds.

Implementation

  • Lives in pkg/authcore as an atomic Consume operation (not a split Has/Add).
  • Two backends:
    • Memory (memory.go) — mutex-atomic, for a single instance.
    • Redis (redis.go) — uses SET NX EX so concurrent calls for the same key return exactly one success, with TTL preserved. Errors fail closed for protected paths.
  • Multi-replica safety: running more than one replica requires the Redis store. The config validation rejects multi-replica without Redis, and the Helm chart refuses to render it.
  • Ordering: replay is consumed after policy checks pass, so unauthorized junk cannot burn a victim's jti.

Example

The provider/gateway demo sends the same accepted request twice and shows the second denied:

make demo-provider-gateway
# ... valid call allows ...
# ... duplicate call -> { "allowed": false, "reason": "jti_replay" }

Concurrency is covered by regression tests proving only one of N concurrent duplicates succeeds, in both the in-memory and Redis paths. These run as part of make test and the acceptance gate.

To use Redis in a multi-replica deployment, the chart expects:

replay:
store: redis
multiReplica: true
redis:
addr: "redis.example:6379"

…and fails render if multiReplica/replicas > 1 is set with store: memory.

What to notice

  • Replay protection is a safety property of acceptance, not an afterthought: it is consumed only on otherwise-valid requests, and only once.
  • "Fail closed" means a Redis outage denies protected requests rather than silently allowing replays.

→ Next: Bundle policy & freshness.