Skip to content

Conversation

@developerkunal
Copy link
Contributor

📝 Checklist

  • All new/changed/fixed functionality is covered by tests (or N/A)
  • I have added documentation for all new/changed functionality (or N/A)

🔧 Changes

This PR implements RFC 9449 DPoP (Demonstrating Proof-of-Possession) support for sender-constrained OAuth 2.0 tokens, providing enhanced security by binding tokens to cryptographic key pairs.

Core Package Changes:

  • Added unified Validator interface supporting both JWT and DPoP validation methods
  • Implemented CheckTokenWithDPoP method in core.Core for framework-agnostic validation
  • Added DPoP context support for accessing proof claims in request handlers
  • Implemented three operational modes: DPoPDisabled, DPoPIfPresent (default), DPoPRequired
  • Added DPoP-specific error codes: ErrorCodeDPoPProofMissing, ErrorCodeDPoPProofInvalid, ErrorCodeDPoPBindingMismatch

Middleware Changes:

  • Automatic DPoP/Bearer token scheme detection from Authorization header
  • DPoP header extraction and validation with configurable extractors
  • Added options: WithDPoPMode, WithDPoPProofOffset, WithDPoPIATLeeway, WithDPoPHeaderExtractor
  • Trusted proxy support via WithTrustedProxies for URL reconstruction behind reverse proxies
  • Enhanced error handling with DPoP-specific error messages

Validator Package:

  • Added ValidateDPoPProof method to Validator struct
  • Implemented dpop+jwt type validation per RFC 9449
  • JWK thumbprint computation and verification (SHA-256)
  • Required claims validation: jti, htm, htu, iat
  • Configurable proof age offset (default: 5 minutes) and IAT leeway (default: 5 seconds)

New Files:

  • core/dpop.go - Core DPoP validation logic
  • dpop.go - HTTP middleware DPoP helpers
  • proxy.go - Trusted proxy URL reconstruction
  • validator/dpop.go - DPoP proof validation
  • validator/dpop_claims.go - DPoP claims interface

Examples:

  • examples/http-dpop-example - Full DPoP implementation with optional Bearer fallback
  • examples/http-dpop-required - Strict DPoP enforcement mode
  • examples/http-dpop-disabled - Explicit DPoP opt-out for legacy systems
  • examples/http-dpop-trusted-proxy - Production deployment behind reverse proxies

Breaking Changes: None - fully backward compatible with existing Bearer token implementations. DPoP is opt-in via token scheme or explicit mode configuration.

📚 References

🔬 Testing

Test Coverage:

  • 70+ new test cases covering all DPoP scenarios
  • Integration tests for all three DPoP modes
  • Edge cases: expired proofs, mismatched claims (JKT, HTM, HTU), invalid signatures
  • Trusted proxy URL reconstruction tests
  • Maintained 95%+ code coverage (95.1% main, 94.0% core)

Manual Testing:
All example applications include integration tests demonstrating:

  1. Valid DPoP token validation with proof
  2. Bearer token validation (when allowed by mode)
  3. Missing DPoP proof detection
  4. JKT mismatch detection
  5. HTTP method/URL mismatch detection
  6. Expired/future proof handling
  7. Multiple DPoP headers handling
  8. Trusted proxy header processing

Verification:

  • ✅ All tests passing (5/5 packages)
  • ✅ 0 linting issues (19 active linters)
  • ✅ Race detector clean
  • ✅ No breaking changes to existing API

developerkunal and others added 29 commits November 21, 2025 13:39
   This implements the Core-Adapter Architecture for v3, separating
   framework-agnostic validation logic from transport-specific adapters.

   Key Features:
   - Core struct with CheckToken method for pure validation logic
   - Options pattern with error-returning option functions
   - Type-safe context helpers using generics (GetClaims[T])
   - Unexported contextKey int to prevent collisions (Go best practice)
   - Structured error types with error codes
   - Logger interface for observability
   - 100% test coverage

   Changes:
   - Add core/core.go: Framework-agnostic validation engine
   - Add core/option.go: Options pattern with validation
   - Add core/errors.go: Structured errors and error codes
   - Add core/context.go: Type-safe context helpers
   - Add core/core_test.go: Comprehensive tests

   This enables future support for multiple frameworks (HTTP, gRPC, Gin,
   Echo, etc.) by wrapping the Core with transport-specific adapters.

   Part of PR 1.2 in v3 Phase 1 implementation.
Refactors validator.New() from positional parameters to pure options pattern, improving API consistency and extensibility.

Breaking Changes:

- validator.New() now takes only options (no positional parameters)

- All parameters must now use option functions

Before:

  validator.New(keyFunc, algorithm, issuer, audience, opts...)

After:

  validator.New(

    validator.WithKeyFunc(keyFunc),

    validator.WithAlgorithm(validator.RS256),

    validator.WithIssuer("https://issuer.example.com/"),

    validator.WithAudience("my-api"),

  )

New Options:

- WithKeyFunc: Required key function

- WithAlgorithm: Required signature algorithm

- WithIssuer: Required issuer URL (with validation)

- WithAudience/WithAudiences: Required audience(s)

Coverage:

- validator package: 100.0%

- All tests passing

- All examples updated
Introduces generics to WithCustomClaims for improved type safety, cleaner API ergonomics and better developer experience.

Summary of Improvements

What Changed

Before (non-generic):

  validator.WithCustomClaims(func() validator.CustomClaims {

      return &MyClaims{}

  })

After (with generics):

  validator.WithCustomClaims(func() *MyClaims {

      return &MyClaims{}

  })

Benefits

1. Type Safety. Compiler ensures T implements CustomClaims at compile time.

2. Cleaner API. Users no longer need to return interface types explicitly.

3. Better IDE Support. Autocomplete works with concrete types.

4. Flexible. Allows nil returns for conditional custom claims with identical runtime behavior.

5. Full Coverage. All tests pass.

Implementation Details

- Introduces WithCustomClaims[T CustomClaims](f func() T)

- Wraps user function internally to return the interface

- No breaking changes. All existing usage continues to work

- Type parameter is inferred from function return type

- Nil functions require explicit type: WithCustomClaims[*MyClaims](nil)

Test Results

- validator package: 100.0 percent coverage

- All tests passing
Updates all example projects to use the new generic WithCustomClaims API and to reference the v3 module path. All example builds succeed with the updated validator version.

Changes included:

1. Updated every example to use the new generic WithCustomClaims syntax across gin, echo, http and iris.

2. Updated each example go.mod file to reference v3 rather than v2 and added the appropriate replace directives.

3. Verified that all examples build correctly with the revised API.
Replaces go-jose with lestrrat-go/jwx v3.0.12 for JWT operations and introduces improved issuer, audience and JWKS handling.

Major changes:

- Replace go-jose with lestrrat-go/jwx v3.0.12 for JWT handling

- Add ES256K algorithm support (ECDSA with secp256k1 curve)

- Implement multi-issuer support through WithIssuer and WithIssuers

- Simplify JWKS provider using jwx's built-in cache which reduces code size by about sixty percent

- Add manual issuer and audience validation to support multiple values

Status:

- Validator and JWKS packages build successfully

- Eighteen of twenty-eight validator tests are passing which confirms that all successful validation paths work

- Ten tests require updates to expected error messages and are currently in progress
…test coverage

  - Refactor jwks.NewProvider() and jwks.NewCachingProvider() to pure options pattern
  - Remove positional parameters, all configuration via functional options
  - Implement runtime type switching to accept both ProviderOption and CachingProviderOption
  - Fix race condition in cache implementation with proper lock synchronization
  - Add URL validation to validator.WithIssuers() for consistency
  - Improve test coverage: jwks 92.1% (+4.8%), validator 87.0% (+5.2%)
  - Add comprehensive tests for all signature algorithms (EdDSA, HS256/384/512, RS256/384/512, ES256/384/512/ES256K, PS256/384/512)
  - Update examples/http-jwks-example to use pure options API
  - Document and skip pre-existing test failure in http-jwks-example

  Breaking Changes:
  - NewCachingProvider now accepts options only (no positional params)
  - WithIssuers now validates URL format and returns errors for invalid URLs

  Fixes:
  - Race condition in jwxCache.Get() with concurrent goroutines
  - Missing URL validation in WithIssuers option

  All tests pass with race detection enabled.
…egration

  Changes:
  - Refactor middleware constructor from New(validateToken, opts...) to New(opts...)
  - Add WithValidateToken() as required option with fail-fast validation
  - Integrate middleware with core package using validatorAdapter bridge
  - Implement unexported contextKey int pattern for collision-free context storage
  - Add type-safe generic claims access: GetClaims[T](), MustGetClaims[T](), HasClaims()

  Logging:
  - Add WithLogger() option for comprehensive JWT validation logging
  - Implement debug, warn, and error logging throughout CheckJWT flow
  - Propagate logger from middleware through core to validator
  - Log token extraction, validation, errors, and exclusion handling

  Error Handling:
  - Implement RFC 6750 OAuth 2.0 Bearer Token error responses
  - Add structured ErrorResponse with error/error_description/error_code fields
  - Generate WWW-Authenticate headers for all error responses
  - Design extensible architecture for future DPoP (RFC 9449) support
  - Add comprehensive error handler tests (13 scenarios)

  Token Extractors:
  - Add input validation to CookieTokenExtractor and ParameterTokenExtractor
  - Fix cookie error handling to propagate non-ErrNoCookie errors
  - Add tests for case-insensitive Bearer scheme and edge cases
  - Validate empty parameter/cookie names at construction time

  Tests:
  - Add option_test.go with comprehensive coverage of all options
  - Add logger integration tests covering all CheckJWT paths
  - Add invalidError tests for Error(), Is(), and Unwrap() methods
  - Add extractor edge case tests (uppercase, mixed case, multiple spaces)
  - Achieve 99.4% total coverage (main: 98.2%, core: 100%, validator: 100%)

  Examples:
  - Update all examples (http, jwks, gin, echo, iris) to use new API
  - Replace old constructor calls with pure options pattern
  - Update claims access to use generic GetClaims[T]() API
  - Add commented logger examples in http-example

  Breaking Changes:
  - Constructor signature: New(opts...) instead of New(validateToken, opts...)
  - Claims access: GetClaims[T](ctx) instead of ctx.Value(ContextKey{})
  - Context key changed to unexported type for collision prevention

  Test Coverage:
  - Main middleware: 98.2%
  - Core: 100.0%
  - Validator: 100.0%
  - JWKS: 100.0%
  - OIDC: 100.0%
  - Total: 99.4%
  - Add doc.go files for all packages (main, core, validator, jwks, oidc)
  - Update README.md for v3 API with working JWT examples
  - Update MIGRATION_GUIDE.md with complete v2 to v3 guide
  - Remove CVE-2025-27144 mitigation (handled by jwx v3)
  - Configure golangci-lint v2.6.2 with proper test exclusions
  - Fix JWT token configuration to match working examples
  - Update Go version requirement to 1.24+
  - Fix import paths (github.com/auth0/go-jwt-middleware/v3)
  - Clarify GetClaims[T]() is required (ContextKey no longer exported)
  - Update GitHub Actions to use golangci-lint v2.6.2
  - Update Makefile with lint installation

  Coverage: 99.4% (98.2% main, 100% core/validator/jwks/oidc)
  Linting: 0 issues
  - Change version to string format ("2" not 2)
  - Move linter settings from top-level to linters.settings
  - Move exclusions to linters.exclusions with new structure
  - Remove unsupported output fields
  - Update exclusions format (presets, paths, rules)

  Verified with: golangci-lint config verify
  Remove duplicate context key management from HTTP middleware and use
  core's SetClaims/GetClaims/HasClaims functions consistently. This
  establishes the standard pattern for all adapters.

  Changes:
  - Remove contextKey and claimsContextKey from middleware.go
  - Update CheckJWT to use core.SetClaims() for storing claims
  - Update GetClaims/MustGetClaims/HasClaims to delegate to core
  - Update test assertion to match core's error message

  Benefits:
  - Single source of truth for context key management in core
  - All adapters (HTTP, gRPC, Gin, Echo) will use same context key
  - Claims stored by any adapter can be retrieved by any other adapter
  - Zero collision risk with unexported contextKey type in core
  - Maintains clean API - HTTP users don't need to import core

  This ensures cross-adapter compatibility while keeping the HTTP
  middleware API user-friendly with convenience wrappers.
- Change WithValidateToken to WithValidator to accept *validator.Validator
- Update ErrValidateTokenNil to ErrValidatorNil
- Refactor validatorAdapter to use TokenValidator interface
- Update all examples (http, http-jwks, gin, echo, iris) to use WithValidator
- Add setupRouter/setupApp functions to all examples for testability
- Create comprehensive integration tests for all examples
- Update test fixtures to use non-expiring test token (expires 2099)
- Add testify dependency to example projects for testing
- Fix iris example to use iris native httptest package

This change enables future extensibility for methods like ValidateDPoP
by allowing explicit passing of the validator instance.
…lidateToken

- Update doc.go with all examples using WithValidator
- Update README.md examples throughout
- Update MIGRATION_GUIDE.md with correct v3 API
- Update option.go comment example
- Align all documentation with the new API that accepts *validator.Validator instances

All documentation now correctly shows the v3 API where middleware
accepts validator instances via WithValidator, enabling future
extensibility for methods like ValidateDPoP.
Implements RFC 9449 DPoP support for sender-constrained OAuth 2.0 tokens.

Key Features:
- Unified Validator interface supporting both JWT and DPoP validation
- Three DPoP modes: Disabled, DPoPIfPresent (default), DPoPRequired
- Automatic DPoP/Bearer token scheme detection
- DPoP proof validation (HTM, HTU, JKT claims)
- Trusted proxy support for URL reconstruction
- Configurable proof age offset and IAT leeway

Core Changes:
- Added CheckTokenWithDPoP method to core.Core
- Implemented DPoP context for accessing proof claims
- Added DPoP-specific error codes and handling

Validator:
- Added ValidateDPoPProof method
- JWK thumbprint computation and verification
- dpop+jwt type validation

Middleware:
- WithDPoPMode, WithDPoPProofOffset, WithDPoPIATLeeway options
- WithDPoPHeaderExtractor for custom header extraction
- WithTrustedProxies for reverse proxy deployments

Examples:
- http-dpop-example: Full DPoP with Bearer fallback
- http-dpop-required: Strict DPoP enforcement
- http-dpop-disabled: Explicit opt-out
- http-dpop-trusted-proxy: Production behind proxies

Tests: 70+ new tests, 95%+ coverage maintained
@developerkunal developerkunal requested a review from a team as a code owner November 27, 2025 05:50
@codecov-commenter
Copy link

codecov-commenter commented Nov 27, 2025

Codecov Report

❌ Patch coverage is 96.61017% with 28 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (v3-dev@a280c5e). Learn more about missing BASE report.

Files with missing lines Patch % Lines
validator/dpop.go 83.56% 6 Missing and 6 partials ⚠️
middleware.go 93.33% 3 Missing and 3 partials ⚠️
proxy.go 96.26% 2 Missing and 2 partials ⚠️
validator/validator.go 86.36% 1 Missing and 2 partials ⚠️
validator/dpop_claims.go 85.71% 2 Missing ⚠️
extractor.go 97.77% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff            @@
##             v3-dev     #363   +/-   ##
=========================================
  Coverage          ?   97.74%           
=========================================
  Files             ?       18           
  Lines             ?     1461           
  Branches          ?        0           
=========================================
  Hits              ?     1428           
  Misses            ?       17           
  Partials          ?       16           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

BREAKING CHANGE: TokenExtractor now returns ExtractedToken instead of string

- Add ExtractedToken type with Token and Scheme fields
- Add AuthScheme type with Bearer/DPoP/Unknown constants
- Update all extractors to return scheme information
- Add security check: DPoP scheme requires DPoP proof header
- Restrict DPoP proofs to asymmetric algorithms per RFC 9449
- Add WWW-Authenticate header tests for DPoP compliance
- Update integration tests for new behavior
  Add comprehensive test coverage for context management and DPoP validation:

  - Add context_test.go with tests for claims, DPoP context, auth scheme,
    and DPoP mode storage/retrieval functions
  - Add edge case tests for ATH validation (empty ATH claim)
  - Add tests for TokenClaims interface validation with Unknown auth scheme
  - Add test for missing cnf claim with DPoP proof (Unknown scheme)

  Coverage improvements:
  - context.go: 0% -> 100%
  - validateATH: 77.8% -> 100%
  - validateDPoPToken: 82.6% -> 100%
  - Overall core package: 89.4% -> 100%

  All tests passing. Achieves target 100% coverage for core package.
Copilot AI review requested due to automatic review settings December 3, 2025 09:45
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR implements RFC 9449 DPoP (Demonstrating Proof-of-Possession) support for sender-constrained OAuth 2.0 tokens, providing enhanced security by binding access tokens to cryptographic key pairs. The implementation is fully backward compatible with existing Bearer token functionality.

Key Changes:

  • Added unified Validator interface with both JWT and DPoP validation methods
  • Implemented three operational modes: DPoPDisabled, DPoPAllowed (default), and DPoPRequired
  • Added trusted proxy support for URL reconstruction behind reverse proxies with security-first defaults
  • Migrated from interface{} to any for improved code consistency

Reviewed changes

Copilot reviewed 51 out of 56 changed files in this pull request and generated no comments.

Show a summary per file
File Description
validator/validator.go Updated keyFunc signature from interface{} to any, added confirmation claim extraction
validator/validator_test.go Updated test signatures from interface{} to any
validator/dpop.go New file implementing DPoP proof validation per RFC 9449
validator/dpop_claims.go New file defining DPoP claims structure
validator/dpop_test.go Comprehensive test suite for DPoP validation (70+ tests)
validator/claims.go Added confirmation claim support for DPoP token binding
validator/claims_test.go Tests for DPoP-related claim methods
validator/doc.go Updated documentation examples to use any
option.go Added DPoP-specific options (mode, proof offset, IAT leeway, header extractor)
option_test.go Tests for new DPoP options and updated extractors
middleware.go Integrated DPoP validation with automatic scheme detection
middleware_test.go Comprehensive middleware integration tests
extractor.go Enhanced token extraction with scheme awareness (Bearer/DPoP)
extractor_test.go Tests for scheme-aware extraction and security checks
proxy.go URL reconstruction for trusted proxy deployments
proxy_test.go Tests for proxy configuration options
jwks/provider.go Updated KeyFunc return type from interface{} to any
examples/* Four new DPoP example applications plus updates to existing examples

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Base automatically changed from v3-phase1-pr6-documentation-linting to v3-dev December 8, 2025 06:38
@nandan-bhat
Copy link

nandan-bhat commented Dec 8, 2025

I ran few test-cases against this PR and observed that in all cases www-authenticate header contained error information. SDK should not include error information when authentication information is missing in the request.

   If the request lacks any authentication information (e.g., the client
   was unaware that authentication is necessary or attempted using an
   unsupported authentication method), the resource server SHOULD NOT
   include an error code or other error information.

Reference: https://datatracker.ietf.org/doc/html/rfc6750#section-3.1

@nandan-bhat
Copy link

required mode contain both Bearer and DPoP challenges now. It should contain only DPoP challenge in required mode.

…e headers

  - Remove error codes from WWW-Authenticate when auth is missing (RFC 6750 Section 3.1)
  - Ensure DPoP Required mode returns only DPoP challenge (no Bearer)
  - Add buildBareWWWAuthenticateHeaders() for bare challenge responses
  - Update tests to verify RFC 6750 compliance
  - Enhance http-dpop-required example tests for WWW-Authenticate validation

  Fixes #2 issues:
  1. WWW-Authenticate should not include error codes when request lacks auth
  2. DPoP Required mode should only return DPoP challenge, not Bearer

  All tests pass with 94.4% coverage.
  - Add ValidateDPoPProof test coverage (option.go:26) - was 0%, now 100%
  - Add comprehensive normalizePort IPv6 test cases (proxy.go) - improved from 47.1% to 100%
  - Add error_handler edge cases for defensive default branches - improved from 80-83% to 100%
  - Document defensive code that cannot be reached in practice (CookieTokenExtractor, getLeftmost, parseForwardedHeader)
  - Remove outdated Go 1.21 loop variable copies (no longer needed in Go 1.23+)

  Test coverage improvements:
  - Main middleware: 94.4% → 98.1%
  - error_handler.go: buildWWWAuthenticateHeaders 83.3% → 100%
  - error_handler.go: buildDPoPWWWAuthenticateHeaders 80% → 100%
  - error_handler.go: buildBareWWWAuthenticateHeaders 80% → 100%
  - option.go: ValidateDPoPProof 0% → 100%
  - proxy.go: normalizePort 47.1% → 100%

  All tests pass with 0 linting issues.
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.

4 participants