20

The openid connect specification adds a nonce parameter to the authorize endpoint, which must be echoed back as a claim in the id_token. It claims that the purpose of this parameter is to prevent replay attacks and has some implementation suggestions around using http only cookies.

I understand what replay attack is in general, but I'd like to get a better understanding of the details of what a replay attack this might protect against might look like, so I can have a better idea about whether I have implemented it effectively, or made some subtle mistake.

I assume the replay attack is against the authorize endpoint, for some third party to get a token corresponding to a different user. Is that it or is it something else? What level of information do we expect the attacker has to replay? Do they have the traffic between the user agent and authorization server but not the client application, which is why they'd be able to replay the traffic to the authorize endpoint but would not be able to replay previous a cookie to the client application? Or they have the browser history of both but not the network traffic so they don't have old cookie values? Does it matter whether or not the user agent still has the previous user's session with the authorization server when the replay occurs? If the previous user does have a valid session with the authorization server and the attacker has access to that, can't they get access to a token without doing a replay? If they don't have a valid session with the authorization server, won't the user be prompted anyway, even if they replay an old request? If the user will be prompted to authenticate, presumably with credentials they don't have, what's the danger of a replay?

I have done a lot of searching and there are some similar questions (Replay attack example for validating nonce? and Purpose of nonce validation in OpenID Connect implicit flow) neither of these actually answers the question about how does this specific replay attack work. The first just quotes the recommended implementation form the spec, then talks about leaked tokens, which seems different from a replay attack to me (if the attacker has a leaked token, haven't they already won anyway?). The second explains the general idea of a replay attack, but doesn't talk about how this particular attack actually works.

As some background I'm making an openid connect app using the authorization code flow (so replaying the redirect from the AS to my app shouldn't do anything because you can only exchange the same authorization code for a token once AFAICT, so you would need to replay the authorize endpoint as well for a replay attack to be meaningful.) My app is using tokens I get back as bearer tokens to authenticate requests to my web server, and they are the entirety of the session state, there is no other meaningful session state or locally managed identity to tie the tokens to. My openid connect provider requires a nonce because it's a security best practice. Is it actually useful in my particular use case? Is the reason I'm so confused because doesn't actually provide meaningful protection for the particular thing that I'm doing? If this does have security implications for me I 110% want to make sure I do the right thing but I'm having a very hard time understanding what it protects me against.

Some implementation detail questions I'm fuzzy on due to not understanding the threat vector exactly:

  • Do I validate the nonce matches what was sent to the authorize endpoint after pulling it back from the token endpoint and then assume it's good for the session? Or should validating the id_token cookie against the nonce cookie be part of every request just like checking the signature?
  • The nonce is supposed to be tied to the session but the term nonce implies it should only be used once. If I need to send a user with a valid session back through the authorize endpoint for some reason (get a new id_token with an expiration further in the future? Request a wider set of scopes?) do I create a new nonce? Is it one nonce per session or per call to authorize?
user3072507
  • 301
  • 2
  • 3

2 Answers2

11

Reading your question, I believe the confusion stems from where the replay attack is taking place. The nonce prevents replay attacks against the client (your application), not the authorization server.

I understand that you're using the code flow, but for the sake of simplicity, let's assume you're using the implicit flow in a single-page application and you have no back-end server (e.g. there's no server-side session). Here's the attack vector that the nonce helps to mitigate:

  1. The client application redirects the user agent to the auth server with a response_type of id_token.
  2. The user establishes identity with the auth server, i.e. logs in.
  3. The auth server redirects the user agent back to the client application with an id_token. The response looks something like this: https://your-single-page.app/auth#token_type=bearer&state=some-state&id_token=some-token
  4. An attacker obtains that response, possibly via packet sniffing, client server logs (e.g the web server that's hosting your SPA), the browser's developer tools, shoulder surfing, or some other means.

The nonce helps to prevent the attacker from taking the authorization server's response, pasting it into their URL bar, and establishing identity with your client application. Here's how:

  1. The client application generates a secure random nonce and stores it as is, in clear text, in a cookie, session storage, or somewhere persistent.
  2. The client application hashes that nonce and sends the hash as an authentication request parameter.
  3. When the client application handles the authentication response, it pulls and removes the nonce from persistent storage, hashes it, and compares it against the nonce in the id_token. If they don't match, then the client application refuses to establish identity.

The attacker may have intercepted the response, including the id_token, but here the nonce effectively acts like a password for the client application. The attacker would need the clear-text nonce to directly establish identity with the client application. (I say "directly" because, depending on the application, an attacker may be able to bypass the nonce check.) To be clear, obtaining an auth response only gives the attacker access to a hash of the nonce, not the actual nonce.

The same attack vector is present when using the code flow, albeit harder to successfully leverage. As an example, imagine an attacker intercepts the authentication response. The attacker could then paste the response (the 302 location) into their URL bar and get your client application to make a token request. When the authorization server responds, your client application can verify the nonce in the ID token against something that your server has tied to your user's user agent (e.g. a cryptographically random value that's stored in an HTTP-only cookie). Again, the nonce acts as a password for your client application. I'll point out that to successfully exploit this exact attack vector there's an assumption that the authorization server does not verify that an authorization code has already been used. That's optional in the spec: "If possible, verify that the Authorization Code has not been previously used."

In my opinion, it would be difficult to leverage this type of replay attack when using the code flow if the authorization server prevents authorization codes from being reused (and it should). However, replaying ID tokens with the implicit flow is trivial given access to authentication responses. As such, the nonce is optional in the code flow and required in the implicit flow.

Since the implicit flow was mentioned a number of times in this answer, it's worth pointing out that the OAuth Group recommends not using it due to its numerous security risks, "not all of which have sufficient mitigation strategies." The code flow with PKCE should be used instead.

benbotto
  • 221
  • 2
  • 7
  • Thanks for linking this from my earlier question. However, why should I care that the attacker can authenticate with my client app running on his computer? Assuming the attacker has admin permissions, he can trivially inspect the nonce wherever I store it, or modify my code to skip the nonce check? In short, if this is the attack the nonce was supposed to prevent, it seems pretty useless since it barely raises attack effort? – meriton Aug 05 '19 at 07:26
  • The client app and the auth server should both participate in preventing nefarious auth on someone else's behalf; the nonce is an added fail-safe on the client's side. The attacker cannot necessarily inspect the nonce, only the hash of the nonce: the attacker may intercept/access an auth response, which only has a hash of a nonce. With the implicit flow and no back-end the nonce check could likely be bypassed (at least for a browser-based client). With a back-end it would be difficult (at best) to bypass, even with the implicit flow. And with the code flow it would be even more difficult. – benbotto Aug 05 '19 at 14:58
  • 1
    I am struggling to understand how this helps with the implicit workflow. In that work flow the id_token is returned directly to the client, and the id_token will contain the raw nonce (just base64urlEncoded). At this point though, the nonce seems useless as the attacker has the signed id_token value and can just write this to localStorage/sessionStorage/cookie or wherever the application usually persists it. I cannot see how it improves security of the implicit workflow, but https://openid.net/specs/openid-connect-core-1_0.html#ImplicitAuthRequest says is it required – noggin182 Jan 16 '20 at 15:24
  • @noggin182 The implicit flow is deprecated. It has a number of well-known security issues. However, when using the implicit flow, it's recommended not to store the token in any sort of persistent storage: you should never put a token in a cookie/storage. The nonce simply prevents a shoulder surfer/MITM from copying and pasting a response into their browser and impersonating a legitimate user. If the token is stored purely in memory (which is still bad practice) then it may be difficult to write the token. But tokens really should not be sent to the browser or used for session. – benbotto Jan 16 '20 at 15:31
1

The nonce in question is designed to be related to the HTTP session which is by default out of scope when creating an OpenID token. By HTTP session data is not used to link an OpenID token, which allows a theoretical attacker to sniff an Open_ID token from the network and attempt to "Replay" the credentials against the authorizing server for access. This will succeed because the OpenID IDP has no given methods for identifying a source other than the signature within the token by default.

To counter this, OpenID allows for the authorizing server to leverage this end point authentication nonce to combine the HTTP Session cookie which is only known at the server/client level as a hash value. Assuming the session id is unique and randomly generated per session, this causes an issue for a would be attacker. When the attacker attempts to replay the credentials, they must guess the hash value for the HTTP session the original user had.

Reading through the notes from OpenID on this are helpful:

https://openid.net/specs/openid-connect-core-1_0.html#NonceNotes

The biggest factors should be:

  • The NONCE value should be based on a SECURELY generated random string
  • The NONCE value should never be used twice
  • The NONCE value should tie something the server knows about the client and can keep secret.

I hope this helps.

Connor Peoples
  • 1,421
  • 5
  • 12