All articles

BLOG / SAML REPLAY ATTACK

SAML Replay Attack Prevention: Stopping Reused Assertions

SAML replay attack prevention explained: how signed assertions get reused, which checks stop them, and how to roll out one-time-use tracking safely.

“Validate the signature and keep assertions short-lived” is incomplete advice for SAML replay attack prevention. A copied assertion can pass both checks. The missing control is state: the application must remember that it has already consumed the assertion and reject another submission before issuing a session.

That decision must hold across concurrent requests, application restarts, and every instance handling sign-in. The practical question isn't just whether the assertion is authentic. It's whether the service can prove that this request won the right to consume it.

What a SAML Replay Attack Reuses

A SAML assertion is a signed XML message carrying an identity provider's statement that a user authenticated. The identity provider (IdP) is the system that signs users in. The service provider (SP) is the application that trusts that statement and creates a local session.

A SAML replay attack happens when someone captures a valid assertion and submits it again to obtain a session. Nothing needs to change in the signed content, so its signature can still verify.

Consider a synthetic scenario: a reverse proxy records a SAML response body in a diagnostic log. An attacker who can read that log copies the response and submits it from another machine two minutes later, while the assertion remains acceptable.

A diagram illustrating a SAML replay attack where an impostor reuses a copied SAML assertion to gain access.

Illustrative timeline

Time Event
10:00 IdP issues a signed assertion
Shortly afterward A proxy log captures a copy
10:02 Another host replays the copied response; the signature still verifies

The attacker needs a copy of the assertion, not the user's password. For a bearer assertion, possession can be enough to attempt the handoff unless the SP enforces its acceptance rules.

Authentic doesn't mean unused

The OASIS SAML Security and Privacy Considerations specification recommends the shortest validity period consistent with successful delivery and says the service provider should ensure assertions aren't used more than once. Its replay guidance includes the OneTimeUse condition for appropriate profiles and tracking previously processed assertions.

A declaration in XML isn't a storage mechanism. The SP needs an actual consumed-assertion record and an acceptance path that checks it.

Core distinction: A valid signature proves integrity and the trusted issuer's involvement. It doesn't prove freshness or first use.

Several related threats need separate names:

  • Credential stuffing means trying leaked or reused passwords at a login form.
  • Account takeover is the broader outcome of an attacker gaining control of an account.
  • Session theft means stealing an existing application session credential after sign-in, rather than replaying the SAML handoff.

Check which components log assertions and who can read those logs.

Validation Checks and the One-Time-Use Procedure

Validation checks for SAML assertion reuse

Check What it stops Action on failure
Signature and issuer Forged or altered assertions Reject and log the issuer
Audience restriction Assertion meant for another service provider Reject
Recipient and destination Assertion delivered to the wrong endpoint Reject
NotBefore and NotOnOrAfter Use outside the validity window Reject, allowing only the documented clock-skew tolerance
InResponseTo Response not bound to the request the service sent Reject where the profile uses it
One-time-use marker Reuse of an already consumed assertion Reject and log as a duplicate

These are complementary checks, not interchangeable options. Audience validation restricts which application may accept an assertion; recipient validation restricts the receiving endpoint. Neither prevents the intended application from accepting the same assertion again.

The OASIS security guidance treats assertion reuse as a security concern and repeated identifiers or artifacts as possible intrusion signals. An SP shouldn't assume that a federation integration supplies replay protection merely because it supports signature validation.

Reject on any failed check. Exceptions, unavailable dependencies, and malformed values must not fall through to the successful authentication branch.

Build an acceptance path with explicit gates

  1. Verify the signature and trusted issuer before using assertion claims.
    Some XML parsing is necessary to verify an XML signature, so “verify before parsing anything” isn't a workable implementation rule. The correct boundary is to parse with a hardened SAML library, verify the expected signed object against configured trust, and only then consume identity claims.

    The common slip is extracting a username or tenant from unverified XML and allowing it to influence authorization or trust selection. Another is validating a signature somewhere in the document while reading claims from a different element. The application must use the exact assertion its library validated.

  2. Match the service, endpoint, and request context.
    Confirm that AudienceRestriction identifies the expected SP and that SubjectConfirmationData Recipient matches the assertion consumer service, the endpoint receiving the response. Validate the response destination as required by the binding and profile.

Where the flow uses InResponseTo, match it to an outstanding authentication request. Merely checking that the field exists doesn't establish a relationship with a request the SP sent. The expected request must come from trusted server-side correlation state.

For teams configuring a Google Workspace integration, the Splash Access guide to Google Workspace SAML provides configuration context. Configuration guidance doesn't replace testing the application's runtime validation.

  1. Apply the timing policy consistently.
    Use UTC and the documented clock-skew allowance, meaning the permitted difference between the IdP's and SP's clocks. Apply all relevant assertion and subject-confirmation expiry constraints rather than selecting whichever timestamp is most permissive.

    Comparing server-local time, ignoring a missing required bound, or catching a timestamp error and continuing are acceptance bugs. An invalid time value is a rejection, not an invitation to use a default.

  2. Extract the validated assertion identifier.
    Read the assertion ID from the verified assertion, along with its issuance timestamp where supplied. Reject missing identifiers when the acceptance policy requires them for consumption tracking.

    Namespace the storage key by the trusted issuer and relevant SP trust context. This avoids treating identifiers from unrelated issuers as though they share an identifier space. Don't let an untrusted issuer string choose a different cache namespace before trust validation succeeds.

  3. Atomically claim first use.
    Insert the identifier only if it doesn't already exist, with retention covering its effective acceptance window. “Atomic” means the store performs the decision and write as an indivisible operation.

    An existing marker means rejection. A storage timeout means the application hasn't established first use. Treat those as distinct operational outcomes, but neither should create a session through this acceptance path.

    The common slip is performing a lookup and a later write as separate operations. A second request can arrive between them.

  4. Create the local session only after the claim succeeds.
    Set an independent application-session expiry. A shorter session lifetime is possible when the application's risk policy requires it, but it isn't a universal SAML requirement.

    The OWASP SAML Security Cheat Sheet recommends short response lifetimes, OneTimeUse, and replay detection. Assertion validity governs accepting the handoff; it shouldn't become the application's session-lifetime policy by accident.

    Creating the session before writing the consumption marker reverses the security boundary. If session creation fails after consumption, retain the marker and restart authentication rather than making the assertion reusable.

  5. Record a structured rejection.
    Distinguish duplicate use from signature failures, expired assertions, and storage errors. Attach privacy-conscious correlation identifiers so operators can investigate without preserving a reusable credential.

    Returning a generic sign-in failure to the browser can be appropriate. Recording only that generic failure internally leaves the team unable to tell an attack from a broken deployment.

Test the security property, not just successful login

Run a two-submission test in an authorized staging environment. Submit the same otherwise-valid SAML response again and verify that the duplicate fails, with exactly one application session issued.

Inspect session storage, not just HTTP responses. A failed response doesn't prove that middleware avoided creating a session or setting a cookie before the failure.

Keep downstream transaction guarantees separate. A consumed assertion doesn't prevent a signed-in user or script from submitting checkout twice; the guide to idempotency keys addresses that different requirement.

The code review should end with a concrete answer: which validation-table row, if any, does the current implementation skip?

Lifetimes, Clock Skew and the Replay Window

This synthetic worked example uses illustrative timestamps and a 30-second clock-skew allowance at both ends. These values demonstrate the arithmetic, not a production recommendation.

Assertion timing worked example (synthetic values)

Field or event What it means
NotBefore = 10:00:00 UTC Assertion becomes valid
NotOnOrAfter = 10:05:00 UTC Validity ends; reject at or after this time without an applicable tolerance
Widened acceptance window From 09:59:30 UTC until, but not including, 10:05:30 UTC
Minimum cache retention Keep the entry through 10:05:30 UTC, with an operational margin for processing and clock uncertainty

The OASIS SAML Core specification defines NotBefore as the start of validity and NotOnOrAfter as its exclusive end. Either bound can be omitted in the assertion syntax, so an application can't assume every received assertion automatically has a bounded lifetime.

The acceptance policy must address missing bounds explicitly. For an authentication handoff requiring bounded acceptance, rejecting an insufficiently bounded assertion is safer than inventing an unlimited default.

Calculate retention from the last possible acceptance

In the example, subtracting the tolerance from the start permits acceptance at 09:59:30. Adding it to the end moves the exclusive cutoff to 10:05:30.

A consumption marker therefore must remain available through that widened end. If the store accepts a relative time-to-live, calculate it from the current time to the effective cutoff, then include an operational margin. Don't blindly start a nominal assertion lifetime when the request happens to arrive.

Two implementation mistakes deserve explicit tests:

  • Eviction at the raw expiry: Deleting the marker at 10:05:00 leaves a gap before the widened cutoff in which a duplicate might be accepted.
  • Excessive tolerance: Allowing several minutes of drift can extend every login's acceptance window far beyond the intended delivery allowance.

Parse timestamps as UTC and centralize the comparison policy. Fix clock synchronization problems instead of continually widening tolerance.

The application's session can continue under its own policy after the assertion stops being acceptable. Conversely, an expired or revoked application session shouldn't become valid merely because the original assertion's timestamp hasn't elapsed.

Short windows reduce an attacker's opportunity. They don't decide which submission was first during the allowed interval.

Race Conditions and Multi-Node Deployments

Concurrent requests can expose a replay bug even when sequential tests pass. Both requests query the consumed-ID store, both receive “not found,” and both create sessions before either write becomes visible.

A faster cache doesn't fix that ordering. The decision needs atomic insert-if-absent, not a quick lookup followed by a write.

Replay-cache deployment patterns

Pattern Replay risk Tradeoff
In-memory cache in one process High after restart or across instances Simple, but unsafe for multiple nodes
Shared store with atomic insert-if-absent and TTL Low when the TTL covers the widened window Extra dependency and latency on each login
Relational table with unique constraint on assertion ID Low when cleanup keeps entries through the window Write load and a cleanup job to run
Check-then-insert without atomicity High under concurrent requests Two requests can both pass the check
Cache eviction before the window ends High after eviction Retention must exceed the effective window

NIST SP 800-63C recommends sufficiently unique assertion identifiers, short validity intervals, and tracking consumed assertions so they can't be used again. Making that rule work in a distributed application requires the store's consistency and failure behavior to match the acceptance decision.

Choose storage by its failure semantics

Redis can provide an atomic conditional write with expiration, but the deployment still needs deliberate persistence, eviction, and failover choices. A successful write to a node that later loses recent state can undermine the guarantee if another node accepts the duplicate.

A relational database can use a unique constraint over the issuer-scoped assertion identifier. The insert result decides first use; a prior SELECT doesn't. Cleanup must retain records until they can no longer affect acceptance.

Shared storage adds latency and an availability dependency. Local memory avoids that network hop but loses the consumed set on restart and doesn't coordinate application instances. Sticky sessions aren't a substitute: routing changes and failures can send the replay elsewhere.

Availability rule: An unavailable consumption store means first use wasn't established. It doesn't mean the assertion was unseen.

Tests should include concurrent submissions against different application nodes, restart behavior, storage timeouts, and failover. Memory pressure matters too: a store that evicts consumption markers early has changed the security policy without changing the SAML timestamps.

For a multi-node SP, every accepting node must participate in the same authoritative first-use decision. Regional routing and replication designs must preserve that property rather than assuming eventual replication is sufficient.

Spotting Replay Attempts in Your Logs

A duplicate event needs context before an operator labels it an attack. Source changes, authentication records, request correlation, and the application's session outcome help distinguish credential reuse from a retry after a lost response.

Replay signals and their likely causes

Signal Likely meaning First action
Same assertion ID seen twice from different IPs or user agents Possible replay Compare with the IdP's sign-in records
Audience or recipient mismatches from one IdP Misconfiguration Check metadata and endpoint URLs
Time-window rejections after a clock change or host migration Clock drift Check time sync and the skew tolerance
InResponseTo failures on request-bound flows Broken request binding Check request correlation storage
Duplicate ID after a lost response Legitimate retry Check whether a session was already issued

Preserve evidence without preserving credentials

Record a keyed hash of the issuer-scoped assertion ID for correlation rather than the raw SAML assertion. Include the rejection category, application node, receiving endpoint, event time, and a request correlation reference. Restrict access and retention according to the sensitivity of those records.

Avoid full request-body capture on the assertion consumer endpoint. Debugging an authentication failure shouldn't create another source of reusable assertions.

  • Investigate duplicates: Compare the original acceptance with later attempts and check whether a session was already issued.
  • Group configuration failures: Review mismatches by IdP and deployment version before treating them as hostile traffic.
  • Separate dependency errors: Track consumption-store failures independently from confirmed duplicates.

A synthetic illustrative incident might involve a burst of 40 duplicate identifiers from an address range at 3 a.m. The first action is to compare those identifiers' original acceptance events with the IdP's sign-in records, not to infer compromise from the count alone.

Browser retries can follow a timeout even when the server completed authentication. Clock drift can also produce rejection bursts without an attacker. Neither explanation justifies accepting duplicates; they change the investigation and recovery action.

Teams building alert ownership can use the security monitoring guide. When correlation failures span application components, teams can find enterprise application debugging support through Continuum Solutions, while keeping authentication credentials out of shared diagnostic material.

Roll Out Replay Checks Without Locking Out Users

A safe rollout makes legitimate authentication observable before changing acceptance behavior. Existing signature, audience, and expiry enforcement should remain active while the new consumption control is evaluated.

Sequence the change around measurable outcomes

  1. Inventory trust relationships. Record each IdP and SP pair, the sign-in routes it serves, its request-binding behavior, and every node that can accept the response. Include background diagnostic middleware and proxy logging in that inventory.

  2. Establish a baseline. Add duplicate counting and structured replay telemetry before enabling the new duplicate-rejection rule. Keep this observation period bounded and access-controlled; observation alone isn't replay prevention.

  3. Prove staging behavior. Run the two-submission test from the validation procedure, then exercise concurrent requests and dependency failures. Verify session issuance directly and confirm that failed authentication can restart cleanly.

  4. Enforce selectively. Start with a selected tenant or provider pair, review legitimate completion, and expand only after the results are understood. Assign an owner who can pause expansion or execute rollback.

A useful measure is the proportion of valid sign-in attempts that reach an authenticated application session, segmented by provider and rejection reason. HTTP success rates alone can miss redirect loops, missing cookies, and sessions that never become usable.

Keep browser verification and federation responsibilities separate

MANDATE's Observe mode follows the same review discipline: review grouped evidence and false positives, then confirm a rollback path before enforcement on a high-value route. The Observe mode rollout is a model for that process, not a replacement for SP-side SAML validation; native Verify workflows currently observe, so enforcement availability must remain specific to the supported integration.

Browser verification applies to browser-originated sign-in, signup, and checkout actions. It doesn't establish human identity or guarantee detection of every automated request. MFA, account risk checks, session management, and recovery controls remain responsible for account security.

Before connecting a sensitive route, decide how verification unavailability, retries, and support escalation work. An error must never become a verified result, and rollback must not disable unrelated authentication checks without notice.

Release gate: The change needs an explicit rejection rule, an unavailable-dependency policy, and a legitimate-completion metric.

Write that rejection rule and metric in the change ticket, together with the rollback owner and the condition that triggers rollback.


MANDATE offers browser verification for important website actions without CAPTCHAs, complementing rather than replacing SAML replay controls. Visit MANDATE to evaluate protecting a selected sign-in, signup, or checkout route, beginning with observation and a defined legitimate-completion signal.

More from the blog

WHAT IS A REPLAY ATTACK

Replay Attacks Explained: Examples and Prevention

See how replay attacks reuse valid requests, why token expiry is not enough, and how nonces, one-use proof, and idempotency address different risks.

ONLINE BUYING BOT

Online Buying Bot Playbook: Defenses Without CAPTCHAs

See how buying bots target stock, tickets, and checkout. Compare purchase limits, browser proof, and safe retries without adding CAPTCHA puzzles.

SESSION HIJACKING

Session Hijacking Defenses for High-Value Web Actions

Detect and prevent session hijacking with secure cookies, rotation, server-side revocation, and sensitive-action checks. Includes a control matrix.