OpenID Connect Nonce explained: Where it matters and where it doesn’t. Replay attack revisited
The nonce is rarely implemented and often misunderstood. Take a closer look at the OIDC nonce, replay attacks, and whether the nonce deserves more attention than it gets in modern OIDC flows.
Most of us are familiar with OAuth’s state and PKCE. They are straightforward to understand, relatively easy to implement, and easy to justify in any integration. During penetration tests, we see state verification implemented fairly consistently, and PKCE adoption is growing. Where it is missing, we recommend it for both public and confidential clients: high value, low cost.
But there is one more concept that consistently raises more questions than answers. The nonce. Unlike state and PKCE, which are part of the OAuth specification, the nonce was introduced by OpenID Connect. That distinction alone may explain why it gets so little attention. I almost never see it implemented and verified in the wild, and every discussion about it seems to open new doubts rather than close them. What is the purpose of an ID Token? What exactly is a replay attack in the context of OIDC? Does it matter whether the client is public or confidential?
The nonce does not get a lot of love. It is mostly misunderstood and rarely implemented. Let’s take a moment to figure out whether it deserves more.
Connect with the author on the LinkedIn

OpenID Connect and ID Tokens: The basics
OpenID Connect introduces the concept of an ID Token: a proof of the user’s identity at a given point in time.
The ID Token is designed for authentication, not for direct use as a session mechanism or for communicating with an API. To be precise: the ID Token should serve as the basis for establishing a local session in the Relying Party, but the RP should issue its own session cookie from it rather than passing the ID Token around. For granting access to an API, that is what OAuth and its Access Tokens are for. A good way to see why is to look at the aud(audience) claim inside the ID Token. It is set to the RP’s client ID, not to any API, which makes it clear the token was never intended to travel further. The ID Token is also intended to be single-use.
Best practices for handling ID Tokens, beyond general JWT guidelines, frequently bring up two related concepts: nonces and replay attacks. What is that about?
ID Tokens: Reality check
From my experience, ID tokens are typically used in one of two ways:
- The wrong way: directly as a session token.
- The correct way: to establish a session in the application, represented by a cookie or JWT issued by the RP.
The ID Token should be used only for authentication, not as a session token. It asserts identity at a point in time and is not designed as a session artifact. When the ID Token is used incorrectly as a session token, the whole idea of a replay attack seems pointless. It is used multiple times by design. What replay?
However, when we treat the ID Token correctly and use it to establish an application session identified by a session cookie or JWT, things start to make sense: one ID Token should ideally equal one session. Therefore, if someone steals our ID Token, they should not be able to open a session in the application.
A nonce is one way to mitigate this, binding the token to the specific login flow that produced it. It is worth keeping in mind that this protection operates at the flow level. Interception of the token response in transit is a different threat, addressed by TLS and the use of the authorization code flow over the implicit flow. We will cover the exact implications of that later, when comparing SAML with OIDC. Right now, letโs take a closer look at nonce.
What do we know about nonce?
The Relying Party generates a nonce and includes it in the Authorization Request. The Identity Provider then embeds it as a claim inside the signed ID Token, and the Relying Party verifies that the claim matches the original value it sent.
A nonce in OIDC has two purposes. The first is to associate a client session with an ID Token. It works similarly to other binding mechanisms in the OIDC and OAuth flows:

The second purpose is to prevent replay attacks, which we will look at more closely in a moment.
Replay attack prevention in SAML vs OIDC
The replay attack is not formally defined in the OIDC standard, so it is worth looking at how a similar standard handles it. In SAML, the definition is explicit: a replay attack amounts to resubmission of a form in order to access a protected resource fraudulently. The countermeasure is equally strict: the Service Provider must not accept an assertion it has already processed (SAML Profiles, 4.1.4.5).
In practice, this is enforced server-side. The server maintains a list of IDs of used assertions and rejects anything it has seen before. In a lot of real-world SAML implementations I have encountered, this is actually followed.Interestingly, I have never seen a SAML assertion used as a session token, while using an OIDC ID Token as one is a common mistake, probably because it is a JWT and the instinct is to drop it straight into an Authorization header.
OIDC takes a more relaxed approach, which works for all OIDC flows and suits distributed architectures better. Both a SAML response and an ID Token can be verified without consulting any central database, purely through signature validation and claims checking. But the moment you want to verify that a token has not been used before, that stateless property breaks down. You need to collect used nonces, store them until expiry, and check incoming tokens against that list. In a distributed architecture where sessions are granted based on ID Tokens, that is not a trivial requirement.
This is why, in OIDC, the accepted mitigation against replay attacks leans toward a client-side mechanism rather than a server-side one.
How to implement a nonce in OpenID Connect?
Let’s take a look at an example implementation.

The flow works as follows:
- The user requests to log in.
- The application server generates a cryptographically random value (RandomValue), stores it as an HttpOnly session cookie, and sends the user to the Identity Provider with nonce=Hash(RandomValue) in the request.
- The user authenticates with the Identity Provider, which embeds the nonce in the returned ID Token.
- The application server receives the ID Token and compares its nonce claim against the hash of the session cookie value.
- If they match, the application creates a session, sets a session cookie, and removes the nonce cookie. If they donโt match, the process is aborted and no session is created.
While this looks similar to PKCE, you should not reuse the same values. The nonce and the PKCE code verifier are revealed and compared at different times, by different actors.
One thing worth noting: the OIDC spec recommends using a plain cryptographic hash of the random value. Using an HMAC instead of a plain hash is a reasonable enhancement for confidential clients, since it additionally proves that the nonce was generated by your server. For public clients, a plain cryptographic hash is the appropriate choice.
Taking nonce protection a step further requires storing used nonces server-side until they expire, which works only for confidential clients. If you already have infrastructure for that, for example for invalidating JWTs, and your application is high-risk, it is a great addition. If you would need to build it from scratch, in most cases it is neither required by the specification nor strictly necessary.
Connect with the author on the LinkedIn

Stealing the ID Token โ OIDC threat model
To perform a replay attack, the attacker first needs to get hold of the ID Token. Whether that is realistic depends heavily on the OIDC flow in use:
- Authorization Code Flow (with or without PKCE) for confidential clients: The ID Token is never exposed in the browser. Only an authorization code travels through the front channel. Both the access token and the ID Token are retrieved via a backchannel request to the token endpoint.
- Authorization Code Flow with PKCE for public clients: Unlike confidential clients, there is no trusted server-side layer handling the token exchange. The ID Token lands directly in the client environment, making it more exposed to leakage through browser cache or analytics scripts.
- Hybrid Flow: The ID Token, if requested, is returned immediately in the front channel alongside the authorization code, while the access token is fetched separately like in the authorization code flow.
- Implicit Flow (deprecated): Both the ID Token and the access token are returned directly in the URL. This is the highest risk scenario and the primary reason the flow was deprecated.
As you might expect, the implicit and hybrid flows, where the ID Token is returned in the URL fragment or query string, carry the highest risk. The token can be exposed in server logs and browser history, and might leak to third parties through marketing or analytics scripts. The risk is obvious, which is why the nonce is mandatory for these flows.
The authorization code flow is a different story. Stealing an ID Token that never appears in the URL is significantly harder. That said, I have seen cases where ID Tokens leaked through analytics scripts or into the local browser cache, so the attack surface is not zero.
For confidential clients using the backchannel, stealing the ID Token is close to impossible under normal conditions.
It is also worth being explicit about the attacker model here. The nonce defends against a specific scenario: an attacker who has captured a valid ID Token and attempts to inject it into a new authentication response to open a session of their own. It is not a general-purpose defense.
Accordingly, the nonce does not help when:
- The attacker already has control of the client through Cross-Site Scripting or a compromised device.
- The ID Token is misused as a session token and repeatedly accepted without fresh validation.
Attacks that require Cross-Site Scripting, traffic interception, or malicious browser extensions are out of scope here. If any of those conditions exist, you have far bigger problems than ID Token replay. When XSS or MITM is on the table, all bets are off.
Where the nonce matters and where it doesnโt
The nonce was primarily designed for the implicit and hybrid flows, where the ID Token traveled through the browser and was directly exposed to leakage. In those contexts, its value is obvious and the spec makes it mandatory.
Modern best practice is to avoid returning ID Tokens in the URL. The implicit flow is deprecated, and the hybrid flow, while not formally deprecated, is discouraged by modern security guidance.
Today, most implementations use the authorization code flow with PKCE, which never exposes the ID Token in the URL. For confidential clients where the token exchange happens entirely via the backchannel, the nonce reduces risk minimally. It is still good hygiene, but it is not where the heavy lifting happens. For public clients, where the ID Token lands directly in the client environment, it offers an additional layer of defense, but it also depends on how the ID token is later utilized.
In some cases, it might be possible to create multiple sessions in an application from a single ID Token. It is worth considering to implement a list of active sessions. Invalidating new sessions so that only one is active at a time or limiting session lifetime are also common measures in high-risk applications.
Replay attack definitions and countermeasures are significantly more relaxed in OIDC compared to SAML. This might seem counterintuitive, but it is a deliberate fit to the threat model and ensures the approach works across all flows.