feat: allow overriding PKCE verifier validation via CodeVerifierStrategy#886
feat: allow overriding PKCE verifier validation via CodeVerifierStrategy#886shilps1583 wants to merge 3 commits into
Conversation
…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.
|
|
📝 WalkthroughWalkthroughPKCE verifier validation is now configurable through ChangesPKCE verifier strategy
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
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (3)
handler/pkce/handler.gohandler/pkce/handler_test.gohandler/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.
110d036 to
910a2ce
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (1)
handler/pkce/handler_test.go (1)
415-417: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valuePrefer
nilover[]byte{}forhash.Sum().Using
nilis more idiomatic in Go when you wantSumto 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
📒 Files selected for processing (3)
handler/pkce/handler.gohandler/pkce/handler_test.gohandler/pkce/strategy.go
🚧 Files skipped from review as they are similar to previous changes (2)
- handler/pkce/strategy.go
- handler/pkce/handler.go
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
S256andplainchallenge methods.Bug Fixes
invalid_grant.Tests