Hi Warren,

Regarding how the authorization code can be leaked to the attacker:

I’m assuming that the attacker finds a way to get access to the query 
parameters (specifically the authorization code) in the callback URL.
There are multiple ways to do so, which I have described in slides 9 through 
12: 
https://datatracker.ietf.org/meeting/124/materials/slides-124-oauth-sessa-browser-swapping-01

While URL sharing, referer header, and analytics tools are already covered by 
the recommendations of the specs, logging is very hard to fix because it cannot 
only be solved by client developers, but also by client providers.

For example, if a company is buying a securely implemented client application 
and runs this application on-premises, they must take care that the 
authorization code in the query parameter is not logged in any way.
Examples of where they can be leaked are any involved reverse proxies / load 
balancers, middlewares (e.g., external WAFs), or HTTP proxies on the user side.
Of course, most of these leakage sources can be fixed. However, administrators 
must be aware that OAuth ignores the best practice, not disclosing secrets in 
the query parameter.
Most administrators do not expect that from a widely deployed security standard 
(OAuth/OIDC).


Regarding CSRF and why PKCE with state verification does not fix the issue:

This indeed has become a bit confusing in the discussion, so let me clarify 
that:
We have two security vulnerabilities that both need to be fixed at the same 
time:

1. CSRF attacks (see slide 3).
The attacker performs the authentication steps at the AS (steps 1 through 5 on 
slide 3) and then injects the callback URL in the user’s session to log in the 
user with the attacker’s account.
This can be detected with the state parameter by the client with the state 
parameter.
According to RFC 6749, the client then MUST NOT resolve the authorization code 
for any tokens.
However, this enables Browser-Swapping attacks if the attacker gets access to 
the authorization code, which will then remain unused.

2. Browser-Swapping attacks (see slide 7+8).
The attacker prepares the attack by letting the client initiate the 
authorization request within the attacker’s session. This also lets the 
attacker’s client generate the code verifier and code challenge for the 
authorization request. The attacker then uses CSRF to inject the authorization 
request (including the attacker’s code challenge) into the user’s session and 
lets the user sign in to the authorization server.
Because of the state parameter validation within the user’s session at the 
callback URL, the user’s login to the client fails. Therefore, the 
authorization code remains valid, and the attacker’s client is the only one 
that knows the correct code verifier for this authorization code. 

This can be solved by the user’s client sending the token request after a 
detected CSRF attack anyway, but with the intention of invalidating the 
authorization code instead of receiving a token.
Since the code verifier has been generated within the attacker's session, the 
user’s client cannot know the code verifier.
For authorization code revocation, this is good, because then the attacked 
user’s client sends the token request with an empty or omitted code_verifier 
parameter to the AS, which assumes the authorization code as „used“, so it 
cannot be used afterwards by the attacker, and does not issue a token, because 
PCKE verification fails.

At the same time, this also fixes CSRF attacks, because the authorization 
server will not issue any token to the CSRF-attacked client. Therefore, the 
client will not be logged in with the attacker’s session.

Greetings,
Jonas




> Am 05.11.2025 um 15:59 schrieb Warren Parad <[email protected]>:
> 
> Jonas, I'm just not following here. What does CSRF have to do with the 
> attack? The user simply cannot complete the auth code flow because it doesn't 
> have the code verifier to send in the token request, right?
> 
> The part I'm missing is that I don't understand how the attacker receives the 
> code back from the AS. Are we assuming that the browser is compromised? Could 
> elaborate on how the attacker receives the code? If the user is phished and 
> clicks on a link, then the user would end up back at the application's real 
> url with the code and attempt to complete the flow. But the user agent won't 
> have the code verifier so it cannot complete the flow. But the attacker 
> doesn't have access to the browser, right? So how would extract the code once 
> it is returned?
> 
> On Wed, Nov 5, 2025 at 6:30 PM Primbs, Jonas <[email protected] 
> <mailto:[email protected]>> wrote:
>> 
>> 
>>> Am 05.11.2025 um 12:21 schrieb Filip Skokan <[email protected] 
>>> <mailto:[email protected]>>:
>>> 
>>> That would require the POST request response from the client to set yet 
>>> another session with the authenticated account that is then used/merged 
>>> with the original one in the follow up GET that the user needs to first 
>>> confirm with an explicit action?
>> 
>> For the classic login-via-OIDC use case, the POST endpoint will directly 
>> respond with setting a cookie session for the client.
>> 
>> For the use-cases you described earlier (step-up auth, OIDC account linking) 
>> etc, clicking the confirm link might do well.
>> 
>>> I don't think you're missing anything but the suggestion is beyond the 
>>> realm of practical, usable or suggestable from my perspective.
>>> 
>>> S pozdravem,
>>> Filip Skokan
>>> 
>>> 
>>> On Wed, 5 Nov 2025 at 18:09, Primbs, Jonas <[email protected] 
>>> <mailto:[email protected]>> wrote:
>>>> 
>>>> 
>>>>> Am 05.11.2025 um 10:54 schrieb Filip Skokan <[email protected] 
>>>>> <mailto:[email protected]>>:
>>>>> 
>>>>> > That’s right, but you don’t need a full session for finishing the 
>>>>> > authorization code flow.
>>>>> 
>>>>> That's a rather sweeping statement which is only conditionally true. 
>>>>> There are very specific cases for which in fact this is false (e.g. 
>>>>> step-up, re-auth, account-linking).
>>>>> 
>>>> 
>>>> But couldn’t this be fixed by the user clicking a confirm link on the HTML 
>>>> response of the POST redirect URI endpoint?
>>>> 
>>>> So calling the POST callback URI would submit only the SameSite=none state 
>>>> cookie to implement the CSRF mitigation.
>>>> If the state verification and the token request succeeded, the client 
>>>> responds with a HTML document like „You are authenticated as [the OIDC 
>>>> user] from [the IdP]. Is that right?“.
>>>> Then the user needs to click a „confirm“ button which is a link to a 
>>>> confirmation page. Technically, this is a same-site GET request initiated 
>>>> by the user, so samesite=lax and I think even samesite=strict session 
>>>> cookies will be sent along with this request.
>>>> 
>>>> Or did I miss anything here?
>>>> 
>>>> 
>>>>> S pozdravem,
>>>>> Filip Skokan
>>>>> 
>>>>> 
>>>>> On Wed, 5 Nov 2025 at 16:34, Primbs, Jonas <[email protected] 
>>>>> <mailto:[email protected]>> wrote:
>>>>>> Hi Dick,
>>>>>> 
>>>>>> 
>>>>>>> Am 05.11.2025 um 06:32 schrieb Dick Hardt <[email protected] 
>>>>>>> <mailto:[email protected]>>:
>>>>>>> 
>>>>>>> Jonas
>>>>>>> 
>>>>>>> + Filip given his expertise 
>>>>>>> 
>>>>>>> I tried to follow this thread but got lost.
>>>>>>> 
>>>>>>> My understanding is you are suggesting we recommend 
>>>>>>> response_mode=form_post over response_mode=redirect 
>>>>>> 
>>>>>> * response_mode=query, but yes.
>>>>>> 
>>>>>>> If session cookies are sameSite=lax, they won't to be sent via 
>>>>>>> form_post -- so client web sites would have to set session cookies to 
>>>>>>> sameSite=none -- which is susceptible to CSRF of course, and while 
>>>>>>> there are other ways to protect against CSRF, your attack is CSRF -- so 
>>>>>>> it would seem that if the site has protected against CSRF then the 
>>>>>>> attack can't happen.
>>>>>> 
>>>>>> That’s right, but you don’t need a full session for finishing the 
>>>>>> authorization code flow.
>>>>>> Instead, you just need to remember the state sent in the authorization 
>>>>>> request and (if PKCE is being used) the code_verifier.
>>>>>> This means, when starting the authorization code flow, you can set the 
>>>>>> following cookie when redirecting from the client to the authorization 
>>>>>> request URI:
>>>>>> 
>>>>>> Set-Cookie: state_{state}={code-verifier}; SameSite=none
>>>>>> 
>>>>>> When you will be sent back with the POST request to the client, your 
>>>>>> browser sends this cookie to the client backend (because of 
>>>>>> SameSite=none) and since the POST body contains the same state parameter 
>>>>>> (reflected by the AS), you fulfill the double submit pattern which is a 
>>>>>> well-known CSRF protection.
>>>>>> 
>>>>>> Does this make things clear for you?
>>>>>> 
>>>>>> Greetings,
>>>>>> Jonas
>>>>>> 
>>>>>> 
>>>>>>> What am I missing?
>>>>>>> 
>>>>>>> /Dick
>>>>>>> 
>>>>>>> On Wed, Nov 5, 2025 at 3:56 AM Primbs, Jonas 
>>>>>>> <[email protected] <mailto:[email protected]>> 
>>>>>>> wrote:
>>>>>>>> Hi Neil,
>>>>>>>> 
>>>>>>>>> Am 04.11.2025 um 09:53 schrieb Neil Madden <[email protected] 
>>>>>>>>> <mailto:[email protected]>>:
>>>>>>>>> 
>>>>>>>>> 
>>>>>>>>> 
>>>>>>>>>> On 4 Nov 2025, at 13:37, Primbs, Jonas 
>>>>>>>>>> <[email protected] 
>>>>>>>>>> <mailto:[email protected]>> wrote:
>>>>>>>>>> 
>>>>>>>>>> Hi Neil,
>>>>>>>>>> 
>>>>>>>>>> thanks for your valuable feedback.
>>>>>>>>>> You will find my thoughts inline.
>>>>>>>>>> 
>>>>>>>>>>> Am 04.11.2025 um 04:19 schrieb Neil Madden <[email protected] 
>>>>>>>>>>> <mailto:[email protected]>>:
>>>>>>>>>>> 
>>>>>>>>>>> Thanks for sharing your slides. Although this is an interesting 
>>>>>>>>>>> theoretical attack, I do not think it is much of a concern in 
>>>>>>>>>>> practice. If we take your attack vectors from slide 12:
>>>>>>>>>>> 
>>>>>>>>>>> * Referer header leaks: the default policy in browsers since 2019 
>>>>>>>>>>> is strict-origin-when-cross-origin, which prevents this leak [1].
>>>>>>>>>> 
>>>>>>>>>> I still think it is worth to mention in the draft because even if 
>>>>>>>>>> it’s the default, it does not mean that client implementors do not 
>>>>>>>>>> change it in a bad way.
>>>>>>>>>> Moreover, in modern infrastructures, reverse proxies are often used 
>>>>>>>>>> for path-based routing, which results resources (images, favicons 
>>>>>>>>>> etc) being referenced on the same host but requests will be routed 
>>>>>>>>>> to external parties like CDNs.
>>>>>>>>>> 
>>>>>>>>>>> * URL sharing and analytics tools are already addressed by [2] in 
>>>>>>>>>>> the original OAuth RFC 6749, which already says to avoid 3rd party 
>>>>>>>>>>> analytics on the redirect endpoint and to redirect immediately 
>>>>>>>>>>> after collecting the credentials. I think most providers do in fact 
>>>>>>>>>>> do this?
>>>>>>>>>> 
>>>>>>>>>> Yes, I have never seen the issue with the analytics tools in the 
>>>>>>>>>> wild before, I just added it for completeness.
>>>>>>>>>> Out of the last 10 oauth client penetration tests, of which 6 were 
>>>>>>>>>> affected by the browser swapping attack (the other 4 did not even 
>>>>>>>>>> implement the CSRF protection), only one was affected by the URL 
>>>>>>>>>> sharing.
>>>>>>>>>> However, redirecting to another URL, not including scripts, and 
>>>>>>>>>> ensuring that referrer-policies are applied correctly are all 
>>>>>>>>>> mitigation strategies, but not solve the general issue of sending a 
>>>>>>>>>> secret (the authorization code) as a query parameter, which is a bad 
>>>>>>>>>> practice in general [1].
>>>>>>>>>> 
>>>>>>>>>>> That leaves leakage via logs, which IMO is adequately addressed by 
>>>>>>>>>>> (a) use of TLS (minimising on-path observers) and (b) using 
>>>>>>>>>>> short-lived auth codes. 
>>>>>>>>>> 
>>>>>>>>>> a) That’s correct.
>>>>>>>>>> b) According to RFC 6749, the maximum validity period is 10 minutes. 
>>>>>>>>>> Today, many logging happens in real-time, so my impression was that 
>>>>>>>>>> 10 minutes is enough for attackers with access to logs. Think of a 
>>>>>>>>>> shell script which polls every minute for the logs and greps for the 
>>>>>>>>>> keyword „code“, extracts the authorization code and automatically 
>>>>>>>>>> resolves that.
>>>>>>>>> 
>>>>>>>>> Attackers with real-time access to logs are generally already quite 
>>>>>>>>> privileged (eg they have ssh access to the servers or access to a 
>>>>>>>>> SIEM). The risk from logs mostly comes from them being copied into 
>>>>>>>>> support tickets or archived to insecure S3 buckets and similar 
>>>>>>>>> things. Those things don't generally happen in 10 minutes.
>>>>>>>> 
>>>>>>>> I don’t agree here.
>>>>>>>> Having read-only access to a SIEM system, which provides you real-time 
>>>>>>>> access to logs being collected from a client application or any 
>>>>>>>> involved reverse proxy, load balancer, middleware etc, should not 
>>>>>>>> enable you taking over the client session of any user.
>>>>>>>> This is a behavior, which many service providers will not expect from 
>>>>>>>> an OAuth-compliant software.
>>>>>>>> 
>>>>>>>> Moreover, this increases the criticality of misconfigured HTTP 
>>>>>>>> servers, e.g., an NGINX or Apache web server which allow you to access 
>>>>>>>> the server’s logs in real-time by browsing something like 
>>>>>>>> /../../../../var/logs/nginx/access.log
>>>>>>>> I agree, such misconfigurations should not be in scope of OAuth. 
>>>>>>>> However, this increases the criticality of such a finding from an 
>>>>>>>> information leak to a medium or even high - and OAuth could improve 
>>>>>>>> that by slightly changing the specs.
>>>>>>>> 
>>>>>>>>>> 
>>>>>>>>>>> Using fragments or form_post both have significant downsides. 
>>>>>>>>>>> Fragments only work with Javascript, which introduces new failure 
>>>>>>>>>>> cases and attack surface (ideally the redirect endpoint would be 
>>>>>>>>>>> served with CSP script-src=none). 
>>>>>>>>>> 
>>>>>>>>>> Yes, that’s why I recommend this for web apps running in the user’s 
>>>>>>>>>> browser with javascript anyways, or for mobile apps.
>>>>>>>>> 
>>>>>>>>> Re-reading my old blog post 
>>>>>>>>> (https://neilmadden.blog/2019/01/16/can-you-ever-safely-include-credentials-in-a-url/)
>>>>>>>>>  reminds me of a drawback of using the fragment: if you don't 
>>>>>>>>> explicitly clear it, then it is carried over on redirects, 
>>>>>>>>> potentially leaking the auth code to other pages/sites. (At least, 
>>>>>>>>> that used to be the case - I don't believe that behaviour has 
>>>>>>>>> changed).
>>>>>>>> 
>>>>>>>> Yes, but we have the same issue with query parameters too, right?
>>>>>>>> Fragment however has the advantage of reducing the attack surface with 
>>>>>>>> respect to logging from an entire landscape down to the client.
>>>>>>>> 
>>>>>>>>> 
>>>>>>>>>> 
>>>>>>>>>>> Form post requires use of samesite=none cookies, weakening CSRF 
>>>>>>>>>>> protections. I don’t think we should be recommending weakening 
>>>>>>>>>>> protections against very real threats (XSS, CSRF) to protect 
>>>>>>>>>>> against something that seems unlikely. 
>>>>>>>>>> 
>>>>>>>>>> Thanks a lot for that hint with the CSRF protection with 
>>>>>>>>>> samesite=none.
>>>>>>>>>> I think samesite=none works perfectly fine, if the „session“ cookie 
>>>>>>>>>> is not a real session cookie, but the state parameter instead, 
>>>>>>>>>> because then the „state“ cookie, which contains the state parameter 
>>>>>>>>>> combined with the identical state parameter reflected by the AS in 
>>>>>>>>>> the POST body parameter apply the double submit cookie pattern [2], 
>>>>>>>>>> which is also a well-known CSRF protection mechanism.
>>>>>>>>>> The real session cookie (if you even need one before you are logged 
>>>>>>>>>> in) could still use samesite=strict or lax.
>>>>>>>>>> But I get the point that maybe I should mention this in the draft.
>>>>>>>>>> 
>>>>>>>>>>> We can perhaps improve the wording around existing countermeasures 
>>>>>>>>>>> if there is evidence they are being ignored, but I think that is 
>>>>>>>>>>> enough. I could be persuaded otherwise, but I’d need to see more 
>>>>>>>>>>> evidence that this is a problem in practice and that the 
>>>>>>>>>>> countermeasures do more good than harm. 
>>>>>>>>>> 
>>>>>>>>>> I think the problem with response_mode=query in general is that the 
>>>>>>>>>> authorization code is transferred in the URL which opens up many 
>>>>>>>>>> attack vectors that all need to be fixed.
>>>>>>>>>> The most critical one are logs because there is no general fix for 
>>>>>>>>>> that, exept not providing the authorization code as a query 
>>>>>>>>>> parameter.
>>>>>>>>>> My impression was that our customers were really surprised that this 
>>>>>>>>>> is still the default behavior of OAuth and they didn’t expect that 
>>>>>>>>>> from such a big large-scale deployed standard.
>>>>>>>>>> Especially our customers, which just deploy existing client 
>>>>>>>>>> implementations, did not expect that they have to care that much 
>>>>>>>>>> about logs of, e.g., their reverse proxies, and they were shocked 
>>>>>>>>>> that these logs which are sometimes audited by external partners 
>>>>>>>>>> enable the log auditor to start a browser-swapping attack and take 
>>>>>>>>>> over, e.g., administrator accounts.
>>>>>>>>> 
>>>>>>>>> I agree in general, but (a) the situation is a lot less bad than it 
>>>>>>>>> used to be and (b) the short-lived nature of auth codes does IMO 
>>>>>>>>> mitigate a lot of these issues. According to 
>>>>>>>>> https://www.oauth.com/oauth2-servers/authorization/the-authorization-response/,
>>>>>>>>>  most auth codes are in fact only valid for 30–60 seconds, which 
>>>>>>>>> would further reduce the opportunity to exploit this. 
>>>>>>>> 
>>>>>>>> That’s right, maybe we should recommend a validity period of 1 instead 
>>>>>>>> of 10 minutes in OAuth 2.1.
>>>>>>>> 
>>>>>>>> By the way, Aaron and I had a discussion on that today and we figured 
>>>>>>>> out that the „implicit flow“ of OIDC might also be affected. Probably 
>>>>>>>> things are much more critical here than in OAuth.
>>>>>>>> 
>>>>>>>> We also discovered an alternative fix for that by enforcing PKCE for 
>>>>>>>> every flow and sending a token request from client to AS even if state 
>>>>>>>> parameters don’t match.
>>>>>>>> This promises to simplify the specs in general with the nice side 
>>>>>>>> effect that it also sufficiently fixes browser-swapping attacks 
>>>>>>>> without breaking changes.
>>>>>>>> I will discuss that with Aaron and Mike and will send an update.
>>>>>>>> 
>>>>>>>>> I'm sympathetic to the idea that query mode is not great, but it's 
>>>>>>>>> pretty well established at this point and we've spent a long time 
>>>>>>>>> telling people to use it. I don't find this a compelling enough 
>>>>>>>>> reason to suggest a change. 
>>>>>>>> 
>>>>>>>> I get your point. However, I’m not a friend of leaving things as they 
>>>>>>>> are, just for not having to correct previous statements, especially if 
>>>>>>>> we could do better.
>>>>>>>> 
>>>>>>>>>> I totally agree, that we cannot simply deprecate response_mode=query 
>>>>>>>>>> for legacy reasons. However, I think we should ensure that client 
>>>>>>>>>> developers and providers are aware of the risk that authorization 
>>>>>>>>>> codes can get logged and can be used to take over user sessions.
>>>>>>>>>> 
>>>>>>>>>> [1] 
>>>>>>>>>> https://owasp.org/www-community/vulnerabilities/Information_exposure_through_query_strings_in_url
>>>>>>>>>> [2] 
>>>>>>>>>> https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#alternative-using-a-double-submit-cookie-pattern
>>>>>>>>> 
>>>>>>>>> — Neil
>>>>>>>> 
>>>>>>>> Independently of whether browser-swapping attacks are a realistic 
>>>>>>>> attack or not — does anyone disagree with adding or at least 
>>>>>>>> referencing response modes in the OAuth 2.1 spec in general?
>>>>>>>> I personally would have been glad to have had any reference to them 
>>>>>>>> from another document instead of being lucky that I accidentially 
>>>>>>>> stumbled over this spec.
>>>>>>>> 
>>>>>>>> Greetings,
>>>>>>>> Jonas
>>>>>>>> 
>>>>>>>> 
>>>>>>>> _______________________________________________
>>>>>>>> OAuth mailing list -- [email protected] <mailto:[email protected]>
>>>>>>>> To unsubscribe send an email to [email protected] 
>>>>>>>> <mailto:[email protected]>
>>>>>> 
>>>> 
>> 
>> _______________________________________________
>> OAuth mailing list -- [email protected] <mailto:[email protected]>
>> To unsubscribe send an email to [email protected] 
>> <mailto:[email protected]>

Attachment: smime.p7s
Description: S/MIME cryptographic signature

_______________________________________________
OAuth mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to