Hi Thomas,

See response inline.

> Am 25.11.2025 um 18:27 schrieb Thomas Broyer <[email protected]>:
> 
> Hi all, re-upping that conversation as a client and client library developer 
> (OIDC relying party actually)
> 
> Do we all agree that the specific scenario where authorization codes leak 
> through near real-time access to access logs can be mitigated by always 
> exchanging the authorization code, even when CSRF is suspected? (absence of 
> or non-matching state; assuming a conformant AS/IdP that has single-use 
> authorization codes)

Yes. I recommend to enforce PKCE in addition and using an authorization code 
which inavlidates the authorization code if no matching code verifier is 
provided in the token request. Otherwise the authorization code will not only 
be invalidated at the AS/IdP, but also resolved for an unwanted 
access/refresh/ID token. Depends on the application whether this is an issue. 
It’s important, that the client does not use these tokens, otherwise you have a 
CSRF attack.

Keep in mind, that in the logging scenario, in theory, the attacker may resolve 
the authorization code before the client does. I think in practice this is 
unrealistic but it may depend on the client implementation.

> response_mode=form_post can also work but has severe implications wrt cookies 
> (requires either using SameSite=None cookies to store "authentication state" 
> between the redirects, or running the AS/IdP and the application "same-site"; 
> Chromium has had a "temporary" Lax+POST mitigation that also allows this to 
> work: 
> https://www.chromium.org/updates/same-site/faq/#q-what-is-the-lax-post-mitigation)

response_mode=form_post is in my opition the best way to mitigate authorization 
code leakage because it solves the URL sharing AND logging issue.

> And the other scenarios (brought in another thread) where the authorization 
> code leaks through a downgrade to response_mode=fragment, or the victim 
> sharing the URL (with authorization code) following an error, can be 
> mitigated by always redirecting, even (particularly) in case of errors, to 
> remove any code from the URL? (I chose to redirect to the same callback 
> endpoint but with an error=, using a custom error value for cases like 
> missing input –likely a response_mode=fragment downgrade– or missing session 
> state –i.e. no cookies–, and always including a hash in the redirect URL –I 
> use the same error=– to remove any authorization code from a 
> response_mode=fragment downgrade)

Yes, but keep in mind that if no specific response_mode can be enforced by the 
AS/IdP, this response_mode mutation may happen independently from the client’s 
expected response mode.
For example, if your client expects response_mode=query or 
response_mode=form_post, the attacker may choose response_mode=fragment.This 
results in the browser being redirected to the client with authorization code 
as fragment parameter, e.g., https://client.example.com/callback#code=a&state=b.
The client backend will expect a POST request to /callback in case of 
response_mode=form_post or a GET request with query parameters in case of 
response_mode=query.
Assuming the client developer has sufficiently implemented the GET endpoint in 
both cases which redirects the user’s browser to 
/callback?error=missing_parameters, the browser’s URL bar will still display 
the fragment parameters, e.g., 
https://client.example.com/callback?error=missing_parameters#code=a&state=b. I 
was also surprised that the browser persists the fragment part of the URL on 
redirects, but historically it makes sense. 
Therefore, I recommend to append an empty fragment part (#) to the end of the 
redirect URL, e.g., redirecting to /callback?error=missing_parameters# because 
only this will also clear the authorization code from the browser’s URL.

> AFAICT, hardly anything can be done if the attacker is able to block the 
> redirect back to the client callback so it never reaches the server. IIUC, 
> this is the only scenario where response_mode=form_post would be the only way 
> to mitigate the attack.
> 
> And for those who deploy AS/IdP (we regularly ship Keycloak alongside our 
> applications), they should configure them (assuming the clients are 
> compatible) to:
> * enforce PKCE

Yes, but this only helps if sending a token request with a valid authorization 
code and an invalid or missing code_verifier makes the AS/IdP invalidating the 
authorization code.
My experience is that most ASs/IdPs do that, but I’m not 100% sure if this is 
specifically covered by any specs.
However, it makes sense for the AS/IdP to invalidate the authorization code, 
because the only legitimate reason for a client having a valid authorization 
code without knowing the correct code verifier is an attack scenario.

> * forbid response_mode=fragment (and implicit grants btw)

I prefer „forbid any response mode not expected by the client“.
The attacker may also change response_mode=form_post to response_mode=query. If 
the client does not implement the GET method at the callback endpoint (because 
it expects a POST request), you will end up with the URL sharing attack because 
the client responds with a method not implemented error and the authorization 
code remains in the query parameters or the URL.

> * follow other best practices (e.g. validating redirect URIs using exact URI 
> matching)

Yes

> Keycloak can do that using Client Policies for instance.

Interesting, I haven’t seen there is a client policy which enforces specific 
response modes. Can you share this? I would be interested for my customers too.

> 
> Am I missing something?

No

> 
> Fwiw, I reproduced those scenarios (near real time access logs leak, 
> response_mode=fragment downgrade, URL sharing) in functional tests of my 
> client library and confirmed the above mitigations work in those cases.
> 
> Spec-wise, I'd suggest including some wording to OAuth 2.1 recommending the 
> above-mentioned mitigations.

+1

One more comment: As Justin mentioned after my presentation at IETF, all 
„solutions" proposed here, are in fact workarounds or mitigation strategies to 
prevent authorization code leakage.
The main problem that we face here is that we do not have any technology to 
reliably verify that the user controlling the client is the same user who logs 
in to the AS/IdP.

@Dick: Stage is your’s ;-)

Jonas


> 
> On Sat, Nov 8, 2025 at 8:00 AM Philippe De Ryck 
> <[email protected]> wrote:
> I believe that the quoted line below is important:
> 
>> On 7 Nov 2025, at 10:42, Frederik Krogsdal Jacobsen 
>> <[email protected]> wrote:
>> 
>> PKCE by itself does not fix this problem, but intentionally using PKCE 
>> without a verifier is one way to revoke a code without getting a token that 
>> you could accidentally use.
> 
> 
> 
> It seems very logical that a client implementation that needs to exchange an 
> authorization code would need a PKCE verifier. It is trying to look it up, 
> but due to this being a malicious or tampered with flow, that lookup is 
> likely to fail (i.e, no PKCE verifier available). If I would write this code, 
> I would not call the AS knowing up front that this request is going to fail. 
> So based on this discussion, it really seems that we should make this a 
> guideline for implementing PKCE on the client. 
> 
> Philippe
> _______________________________________________
> OAuth mailing list -- [email protected]
> To unsubscribe send an email to [email protected]
> 
> 
> -- 
> Thomas Broyer
> /tɔ.ma.bʁwa.je/
> _______________________________________________
> OAuth mailing list -- [email protected]
> To unsubscribe send an email to [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