Skip to content

feat: allow overriding PKCE verifier validation via CodeVerifierStrategy#886

Open
shilps1583 wants to merge 3 commits into
ory:masterfrom
shilps1583:feat/pluggable-pkce-verifier-strategy
Open

feat: allow overriding PKCE verifier validation via CodeVerifierStrategy#886
shilps1583 wants to merge 3 commits into
ory:masterfrom
shilps1583:feat/pluggable-pkce-verifier-strategy

Conversation

@shilps1583

@shilps1583 shilps1583 commented Jul 15, 2026

Copy link
Copy Markdown

Depends on #883. This branch is rebased onto #883, which moves the PKCE
session deletion in HandleTokenEndpointRequest from immediately after
fetch to only after a successful verification, closing a downgrade/replay
window. Without that fix, the CodeVerifierStrategy delegation added here
would sit before the relocated delete calls instead of between them and
reintroduce the vulnerability #883 fixes.

GitHub can't stack this across forks (the base branch selector only
accepts branches that exist in ory/fosite, and #883's branch only exists
on my fork), so the base here is still master. That means this diff
currently includes #883's changes too and will look larger than the
feature itself until #883 merges -- please review #883 first. Once #883
merges, this PR's diff should shrink back down on its own; if it doesn't
(e.g. if #883 is squash-merged), I'll rebase this branch onto the new
master to clean it up.

Handler.HandleTokenEndpointRequest inlines RFC 7636's verifier length,
character-set, and comparison rules directly in one function, with no
seam to change them. Downstream users migrating clients from an
authorization server that predates or relaxes those rules (for
example, no minimum verifier length) currently have to fork the whole
handler to change them, which also means re-implementing the session
lifecycle and EnforcePKCE/EnforcePKCEForPublicClients handling that
has nothing to do with verifier policy.

Extract the length/character-set check and the challenge comparison
into a CodeVerifierStrategy interface, with DefaultCodeVerifierStrategy
preserving today's RFC 7636 behavior as Handler's default when
Verifier is left unset. This is a behavior-preserving refactor for
existing callers; it only adds a new optional Handler.Verifier field.

Closes #885.

Summary by CodeRabbit

  • New Features

    • Added configurable PKCE verifier format validation and challenge-matching strategies.
    • Introduced a default strategy with standard RFC 7636 verifier rules and support for S256 and plain challenge methods.
  • Bug Fixes

    • Improved PKCE token endpoint behavior: when a verifier is provided but no code challenge is registered, the request now fails with invalid_grant.
  • Tests

    • Added coverage to ensure custom strategies are honored and that missing registered challenges still trigger the expected error.

…rification

HandleTokenEndpointRequest deletes the stored PKCE request session
immediately after fetching it, before the verifier has been checked
against the bound challenge. A failed verification (wrong verifier,
disallowed method, PKCE not enforced but a challenge existed, etc.)
still leaves the session deleted.

That session is what binds an authorization code to its
code_challenge. Once it's gone, the same code can be replayed at the
token endpoint with no code_verifier at all: GetPKCERequestSession
returns ErrNotFound, nv == 0, and validateNoPKCE lets the exchange
through as a non-PKCE request (unless EnforcePKCE is on). A failed
PKCE attempt against a code should not make a second, verifier-less
attempt against that same code succeed -- that's a downgrade from a
PKCE-protected exchange to an unprotected one.

Move the delete to the two points where the session's job is actually
done: a confirmed challenge/verifier match, and the non-PKCE path
where no challenge was ever bound and none is required. A verifier
presented against a session with no bound challenge is also consumed,
since there is no challenge there for a downgrade to strip. Every other
error path (validate() failures, verifier format failures, a
challenge/verifier mismatch) now leaves the session in place.

TestHandleTokenEndpointRequest_SessionLifecycle covers both outcomes;
two of its five cases fail against the prior behavior.
@shilps1583 shilps1583 requested review from a team and aeneasr as code owners July 15, 2026 13:12
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

PKCE verifier validation is now configurable through CodeVerifierStrategy. The handler defaults to RFC 7636-compatible validation, delegates format and challenge checks, and consumes sessions only after successful validation or explicit downgrade handling.

Changes

PKCE verifier strategy

Layer / File(s) Summary
Strategy contract and default validation
handler/pkce/strategy.go
Adds configurable format validation and method-specific plain/S256 challenge verification with RFC 7636-compatible defaults.
Handler integration and session lifecycle
handler/pkce/handler.go
Adds the exported Verifier field, delegates validation to the selected strategy, preserves missing-challenge handling, and centralizes session consumption after validation.
Override and lifecycle coverage
handler/pkce/handler_test.go
Tests successful and failed session consumption paths, custom short-verifier acceptance, and missing-challenge rejection before custom challenge validation.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant TokenEndpoint
  participant Handler
  participant CodeVerifierStrategy
  participant Storage
  TokenEndpoint->>Handler: HandleTokenEndpointRequest(code_verifier)
  Handler->>CodeVerifierStrategy: ValidateVerifierFormat(verifier)
  Handler->>CodeVerifierStrategy: ValidateChallenge(method, challenge, verifier)
  CodeVerifierStrategy-->>Handler: Validation result
  Handler->>Storage: DeletePKCERequestSession after validation or downgrade
  Handler-->>TokenEndpoint: Token response or validation error
Loading

Possibly related PRs

  • ory/fosite#883: Also changes PKCE token-endpoint session deletion timing and lifecycle coverage.

Suggested reviewers: aeneasr

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The change set matches #885 by adding a pluggable verifier strategy with default RFC 7636 behavior and preserving handler lifecycle logic.
Out of Scope Changes check ✅ Passed The files shown are all directly related to the PKCE strategy feature and tests, with no clear unrelated code changes.
Title check ✅ Passed The title is concise and accurately summarizes the main change: making PKCE verifier validation pluggable.
Description check ✅ Passed The description explains the change, motivation, and references #885, though the checklist boxes are not filled out.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@handler/pkce/strategy.go`:
- Around line 35-37: Update DefaultCodeVerifierStrategy.ValidateChallenge to
return an error for any method other than the supported empty/plain and S256
values, rather than falling through to plain comparison; keep the documented
mismatch behavior unchanged and ensure the interface contract remains accurate.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f5d5babf-3805-42f3-8b64-34563b1daa92

📥 Commits

Reviewing files that changed from the base of the PR and between a5f0b09 and d033b4f.

📒 Files selected for processing (3)
  • handler/pkce/handler.go
  • handler/pkce/handler_test.go
  • handler/pkce/strategy.go

Comment thread handler/pkce/strategy.go
…tegy

Handler.HandleTokenEndpointRequest inlines RFC 7636's verifier length,
character-set, and comparison rules directly in one function, with no
seam to change them. Downstream users migrating clients from an
authorization server that predates or relaxes those rules (for
example, no minimum verifier length) currently have to fork the whole
handler to change them, which also means re-implementing the session
lifecycle and EnforcePKCE/EnforcePKCEForPublicClients handling that
has nothing to do with verifier policy.

Extract the length/character-set check and the challenge comparison
into a CodeVerifierStrategy interface, with DefaultCodeVerifierStrategy
preserving today's RFC 7636 behavior as Handler's default when
Verifier is left unset. This is a behavior-preserving refactor for
existing callers; it only adds a new optional Handler.Verifier field.
DefaultCodeVerifierStrategy.ValidateChallenge fell through unrecognized
methods to the same comparison used for "plain", even though the
interface docs promised an error when method is unsupported. Handler's
own validate() already rejects unsupported methods before
ValidateChallenge is ever called, so this was never reachable in
practice, but it left the default implementation unsound on its own
and its documented contract misleading to anyone implementing a custom
CodeVerifierStrategy.

Add an explicit case for "plain" and the empty method, and return
ErrInvalidRequest for anything else, matching the error Handler.validate
already returns for the same condition.

Also add a test proving a custom Verifier's permissive
ValidateVerifierFormat does not bypass Handler's own missing
code_challenge guard, which runs independently of the strategy.

Addresses a CodeRabbit review comment on this PR.
@shilps1583 shilps1583 force-pushed the feat/pluggable-pkce-verifier-strategy branch from 110d036 to 910a2ce Compare July 15, 2026 14:04

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
handler/pkce/handler_test.go (1)

415-417: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Prefer nil over []byte{} for hash.Sum().

Using nil is more idiomatic in Go when you want Sum to allocate a new slice, as an empty slice literal still results in a new allocation due to zero capacity but is slightly more verbose.

♻️ Proposed refactor
 	hash := sha256.New()
 	hash.Write([]byte(s256verifier))
-	s256challenge := base64.RawURLEncoding.EncodeToString(hash.Sum([]byte{}))
+	s256challenge := base64.RawURLEncoding.EncodeToString(hash.Sum(nil))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@handler/pkce/handler_test.go` around lines 415 - 417, Update the hash.Sum
call in the PKCE challenge calculation to pass nil instead of an empty byte
slice, while preserving the existing base64 encoding behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@handler/pkce/handler_test.go`:
- Around line 415-417: Update the hash.Sum call in the PKCE challenge
calculation to pass nil instead of an empty byte slice, while preserving the
existing base64 encoding behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7469c60d-555d-4584-9ed9-fb07bfc49869

📥 Commits

Reviewing files that changed from the base of the PR and between 110d036 and 910a2ce.

📒 Files selected for processing (3)
  • handler/pkce/handler.go
  • handler/pkce/handler_test.go
  • handler/pkce/strategy.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • handler/pkce/strategy.go
  • handler/pkce/handler.go

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow overriding PKCE verifier validation without forking the handler

2 participants