A verifier belongs to one transaction
The client creates a high-entropy code_verifier for each request and derives a code_challenge. The challenge travels first, while the verifier is used later, so possession of an intercepted code need not permit token exchange.
S256 hashes the verifier’s ASCII representation with SHA-256 and encodes the result as unpadded base64url. It does not merely publish the verifier under another field name. RFC 7636 requires capable clients to use S256, reinforced by current advice against exposing the verifier.
The server binds a challenge to its code
The server associates the challenge and method with the issued code. At the token endpoint it recomputes the challenge from the submitted verifier and rejects a mismatch, regardless of an earlier approval screen.
RFC 9700 also prevents downgrade by requiring an earlier challenge when accepting a verifier. Adding a verifier field only at exchange time, with nothing bound to compare, cannot establish the required transaction link.
Neither a reusable client secret nor a whole login scheme
Originally motivated by public clients, PKCE is also recommended for confidential clients. Its verifier is transaction-specific, not one permanent shared secret for every installation. A constant challenge loses the required transaction binding.
When server support and binding are established, PKCE can also protect the authorization callback against relevant CSRF attacks. Redirect, token and application checks remain necessary; PKCE does not authenticate every state field or replace authorization enforcement.
References: [2]