The only issue I have with deprecating state for CSRF protection is that the client has no way in general to know if the AS supports (in fact enforces) PKCE. If it doesn’t, then we may end up with no CSRF protection at all, and clients being vulnerable to Login CSRF/session fixation-like attacks.
S1 seems like the cleanest solution to me. I think this should also come with language officially deprecating "state" for CSRF protection like Philippe said.
On Thu, Nov 6, 2025 at 10:59 AM Primbs, Jonas <[email protected]> wrote:
Let’s collect auth code revocation solutions:
S1: Enforce PKCE + normal token request but without code_verifier.
+ No additional endpoints
+ Works for many existing implementations
- AS must implement PKCE and enforce it for all clients (bad for testing)
This makes me wonder if we could in fact have a special client_id value that indicates that the AS should revoke the code (and any tokens if issues)? It's a bit hacky but has the advantage of likely doing the right thing for most ASes, as Tim mentions. Something like client_id=csrf_detected_revoke_please.
a minor (but imho relevant to this discussion) nitpicking inline.
Best,
Tim
On 05.11.25 16:25, Primbs, Jonas wrote:
Hi
Frederik,
yes, calling the token request validly, thereby invalidating
the authorization code for future usage by the attacker, and
throwing away the token response could also be a solution.
However, I am not sure what the implications could be with
respect to how authorization servers handle this (e.g., starting
a session, which confuses users when they look at the list of
active sessions) or how clients handle this (e.g., logging
tokens in a potential crash dump).
If authorization servers implement token revocation
correctly, when authorization codes are used twice, sending a
second valid token request with the same authorization code
afterwards might ensure that the issued tokens cannot be used
anymore.
Again, this might fail if the client faces any issues. So I
prefer a standardized authorization code invalidation mechanism.
One opportunity here, which is already standardized, is
enforcing PKCE and sending no code_verifier in the token request
intentionally.
The issue with that is the (historically grown) lack of precision
in the specs as to when exactly an authZ code is to be invalidated
by the AS. Let me elaborate a bit:
RFC 6749 says (in 4.x) the client MUST only use the code once and
the AS MUST deny all but the first request with a given code (and
SHOULD revoke associated tokens). In 10.5, we have "Authorization
codes MUST be [...] single-use." - without being explicit about
whether this statement applies to the "user" of the code (the
client), the AS, or both; although I'd argue that interpreting
this as "the client may only use it once" is a justifiable
interpretation (especially because the subsequent sentences in
10.5 also just repeat the SHOULD statement from 4.x).
RFC 6819, 4.4.1.1 does say "The authorization server should
enforce a one-time usage restriction (see Section 5.1.5.4)."; but
the language there is not normative ("may", "may want", ...); the
same is true for 5.2.1.1.
OIDC is even more vague (3.1.3.2): The AS MUST ... "If possible,
verify that the Authorization Code has not been previously used."
... just a few examples.
Using PKCE does not change this ambiguity; RFC 7636 does not talk
about code invalidation at all.
In other words: An error response from the AS's token EP, e.g.,
due to a wrong/missing code_verifier does not guarantee that the
code has been invalidated. And as others have pointed out in this
thread, there are AS implementations out there that do accept a
code multiple times (be it "on purpose", or due to CAP). Of
course, one might argue that these are not standards-compliant,
but I don't think there's a very strong case for that claim, given
the (historically) inaccurate wording...
That being said: If I were to implement a client today, I would
make such a "wrong" token request to at least give the AS a chance
of detecting the attack - and if the AS follows the SHOULD-advise
from 6749, any tokens issued for that code would then immediately
be invalidated, which of course does not prevent an attack, but
may help to limit the damage.
Side note: This "best effort" damage control strategy does not
even need PKCE, just sending the code with a wrong client_id
should lead to the same result (from a "did the AS implement
6749's SHOULD" perspective).
If there already is a spec for that in CIBA, we should
include or at least reference this in the OAuth 2.1 spec.
Greetings,
Jonas
Am 05.11.2025 um 04:02 schrieb Frederik Krogsdal
Jacobsen <[email protected]>:
Hi Jonas,
Thanks for the detailed explanation of the attack
and possible mitigations.
It seems to me that your suggestion 3 could be
implemented by the client by simply exchanging the
code and throwing away the token response when the
initial CSRF is detected.
This would of course only work with an AS that
correctly implements the security guidance in section
10.5 of RFC 6749: "Authorization codes MUST be
short lived and single-use."
The main problem with this approach is that
it is a bit confusing to explain.
I also know that in practice, some AS
implementers allow multiple uses of the code, so it
may be interesting to look into defining a specific
"cancel request" that uses up a code without
returning a token.
Defining such a request might also make the
approach easier to explain.
In fact, many OIDC providers already define
custom "cancel" requests to mitigate phishing. A
"cancel" request might also be useful for OpenID
CIBA [1].
It references OpenID Connect’s response modes
(fragment and form_post) as solutions for
Browser-Swapping attacks, which I have presented
in today’s OAuth WG meeting.
I’m interested in your feedback on this first
draft, which currently covers only recommendation
#2 from my slides, because this is probably the
least controversial change.
If you are attending onsite, also feel free to speak
to me in the hallway. My company gave me enough of
the „No, PKCE…“ t-shirts for the rest of the week,
so that it’s easier for you to find me. @Brian &
Mike: I have learned from the best ;-)