Skip to content

wip: jwt and oauth2_introspection authenticators updated to support DPoP#3231

Draft
dadrus wants to merge 11 commits into
mainfrom
feat/dpop_support
Draft

wip: jwt and oauth2_introspection authenticators updated to support DPoP#3231
dadrus wants to merge 11 commits into
mainfrom
feat/dpop_support

Conversation

@dadrus

@dadrus dadrus commented Apr 29, 2026

Copy link
Copy Markdown
Owner

Related issue(s)

closes #2892

Checklist

  • I agree to follow this project's Code of Conduct.
  • I have read, and I am following this repository's Contributing Guidelines.
  • I have read the Security Policy.
  • I have referenced an issue describing the bug/feature request.
  • I have added tests that prove the correctness of my implementation.
  • I have updated the documentation.

Description

This PR introduces support for DPoP (RFC 9449) and enables heimdall to automatically detect DPoP-bound tokens and validate them according to the RFC.

DPoP Configuration Options

Fine-grained control over PoP validation and behavior is provided via a new proof_of_possession property. This property extends the existing assertions configuration used by both the jwt and oauth2_introspection authenticators.

# heimdall config
authenticators:
  - id: jwt_authenticator
    type: jwt
    config:
      metadata_endpoint:
        url: https://auth-server/.well-known/oauth-authorization-server
      # Other existing settings
      assertions:
         # New settings to control DPoP (and other PoP types in the future)
         # As with other existing assertions, these settings can be overwritten
         # at a rule level
         proof_of_possession:
           # Which PoP type is required. Optional.
           type: dpop # If configured, must be dpop
           # PoP type specific configuration. Optional and can only be configured if type is set
           config:
              # The below settings are for dpop type
              # Maximum allowed age of the DPoP proof (based on iat) and the nonce included in 
              # the proof. Optional
              max_age: 1m # Defaults to 1 minute
              # Whether a DPoP proof must contain a nonce requested by heimdall. Optional
              nonce_required: true # Defaults to false
              # Whether replay of DPoP proofs is allowed (based on jti claim). Optional
              replay_allowed: false # Defaults to false

         # The validation of DPoP proofs also makes use of the following previously existing 
         # assertion settings
         validity_leeway: 10s # used to check the time validity of the proof
         allowed_algorithms: # used during signature validation of the proof
           - ES384
           # further algorithms 
      
      # Most probably you want to make use the previously available error signaling as well
      error_signaling:
        enabled: true

Error Signaling Update

This PR also extends error signaling for both authenticators. When enabled, heimdall emits error challenges as defined by the DPoP RFC:

  • If the verification of the DPoP bound token fails, the scheme of the WWW-Authenticate header will be set to DPoP.
  • If nonce_required is enabled, but the DPoP proof does not contain a valid nonce, the error will be set to use_dpop_nonce and the DPoP-Nonce header with a new nonce will be included in the response.
    NOTE: If nonce_required is set to true, a master key must be configured for nonce generation and validation (see below). Otherwise, heimdall will refuse to start (or reject the rule if configured at the rule level).
  • If the DPoP proof is invalid (e.g. missing proof, mismatched token binding, wrong Authorization header scheme, etc.), the error will be set to invalid_dpop_proof.

If error signaling is configured to include error details, the error_description field will contain additional details.

Master Key Settings for Nonce Generation and Validation

Nonces are generated and verified using a symmetric (master) key. For this purpose, a new top-level configuration property has been introduced: TODO

@codecov

codecov Bot commented Apr 29, 2026

Copy link
Copy Markdown

❌ 29 Tests Failed:

Tests completed Failed Passed Skipped
3156 29 3127 0
View the full list of 29 ❄️ flaky test(s)
github.com/dadrus/heimdall/cmd/serve::TestCreateApp

Flake rate in main: 29.17% (Passed 51 times, Failed 21 times)

Stack Traces | 0s run time
Failed
github.com/dadrus/heimdall/cmd/serve::TestRunDecisionModeForEnvoyGRPCRequests

Flake rate in main: 29.17% (Passed 51 times, Failed 21 times)

Stack Traces | 1s run time
Failed
github.com/dadrus/heimdall/cmd/serve::TestRunDecisionModeForHTTPRequests

Flake rate in main: 29.17% (Passed 51 times, Failed 21 times)

Stack Traces | 0.5s run time
Failed
github.com/dadrus/heimdall/cmd/serve::TestRunProxyMode

Flake rate in main: 29.17% (Passed 51 times, Failed 21 times)

Stack Traces | 0.5s run time
Failed
github.com/dadrus/heimdall/cmd/validate::TestValidateConfig

Flake rate in main: 29.17% (Passed 51 times, Failed 21 times)

Stack Traces | 0.27s run time
Failed
github.com/dadrus/heimdall/cmd/validate::TestValidateConfig/no_default_principal_in_default_rule

Flake rate in main: 25.00% (Passed 24 times, Failed 8 times)

Stack Traces | 0.02s run time
Failed
github.com/dadrus/heimdall/cmd/validate::TestValidateConfig/no_https_configured_for_generic_authenticator

Flake rate in main: 29.17% (Passed 51 times, Failed 21 times)

Stack Traces | 0.01s run time
Failed
github.com/dadrus/heimdall/cmd/validate::TestValidateConfig/no_https_configured_for_generic_contextualzer

Flake rate in main: 29.17% (Passed 51 times, Failed 21 times)

Stack Traces | 0.02s run time
Failed
github.com/dadrus/heimdall/cmd/validate::TestValidateConfig/no_https_configured_for_jwks_endpoint_in_jwt_authenticator

Flake rate in main: 29.17% (Passed 51 times, Failed 21 times)

Stack Traces | 0.02s run time
Failed
github.com/dadrus/heimdall/cmd/validate::TestValidateConfig/no_https_configured_for_metadata_endpoint_in_jwt_authenticator

Flake rate in main: 29.17% (Passed 51 times, Failed 21 times)

Stack Traces | 0.01s run time
Failed
github.com/dadrus/heimdall/cmd/validate::TestValidateConfig/no_https_configured_for_oath2_introspection_authenticator_endpoint

Flake rate in main: 27.27% (Passed 48 times, Failed 18 times)

Stack Traces | 0.01s run time
Failed
github.com/dadrus/heimdall/cmd/validate::TestValidateConfig/no_https_configured_for_oauth2_client_credentials_finalizer

Flake rate in main: 29.17% (Passed 51 times, Failed 21 times)

Stack Traces | 0.02s run time
Failed
github.com/dadrus/heimdall/cmd/validate::TestValidateConfig/no_https_configured_for_remote_authorizer_endpoint

Flake rate in main: 29.17% (Passed 51 times, Failed 21 times)

Stack Traces | 0.01s run time
Failed
github.com/dadrus/heimdall/cmd/validate::TestValidateConfig/no_https_in_http_endpoint_rule_provider

Flake rate in main: 29.17% (Passed 51 times, Failed 21 times)

Stack Traces | 0.02s run time
Failed
github.com/dadrus/heimdall/cmd/validate::TestValidateConfig/no_https_in_oauth2_client_credentials_authentication_strategy

Flake rate in main: 29.17% (Passed 51 times, Failed 21 times)

Stack Traces | 0.01s run time
Failed
github.com/dadrus/heimdall/cmd/validate::TestValidateConfig/tls_is_disabled_for_redis_cache

Flake rate in main: 29.17% (Passed 51 times, Failed 21 times)

Stack Traces | 0.03s run time
Failed
github.com/dadrus/heimdall/cmd/validate::TestValidateConfig/valid_config_with_default_rule

Flake rate in main: 25.00% (Passed 24 times, Failed 8 times)

Stack Traces | 0.01s run time
Failed
github.com/dadrus/heimdall/cmd/validate::TestValidateConfig/valid_config_without_default_rule

Flake rate in main: 25.00% (Passed 24 times, Failed 8 times)

Stack Traces | 0.02s run time
Failed
github.com/dadrus/heimdall/cmd/validate::TestValidateRuleset

Flake rate in main: 29.17% (Passed 51 times, Failed 21 times)

Stack Traces | 0.29s run time
Failed
github.com/dadrus/heimdall/cmd/validate::TestValidateRuleset/everything_is_valid_for_decision_mode_usage,_default_and_custom_principals_are_specified

Flake rate in main: 25.00% (Passed 24 times, Failed 8 times)

Stack Traces | 0.03s run time
Failed
github.com/dadrus/heimdall/cmd/validate::TestValidateRuleset/everything_is_valid_for_decision_mode_usage,_no_principals_are_specified

Flake rate in main: 25.00% (Passed 24 times, Failed 8 times)

Stack Traces | 0.03s run time
Failed
github.com/dadrus/heimdall/cmd/validate::TestValidateRuleset/everything_is_valid_for_proxy_mode_usage,_default_and_custom_principals_are_specified

Flake rate in main: 25.00% (Passed 24 times, Failed 8 times)

Stack Traces | 0.04s run time
Failed
github.com/dadrus/heimdall/cmd/validate::TestValidateRuleset/everything_is_valid_for_proxy_mode_usage,_no_principals_are_specified

Flake rate in main: 25.00% (Passed 24 times, Failed 8 times)

Stack Traces | 0.03s run time
Failed
github.com/dadrus/heimdall/cmd/validate::TestValidateRuleset/invalid_for_proxy_usage

Flake rate in main: 29.17% (Passed 51 times, Failed 21 times)

Stack Traces | 0.04s run time
Failed
github.com/dadrus/heimdall/cmd/validate::TestValidateRuleset/invalid_rule_set_file

Flake rate in main: 29.17% (Passed 51 times, Failed 21 times)

Stack Traces | 0.04s run time
Failed
github.com/dadrus/heimdall/cmd/validate::TestValidateRuleset/ruleset_with_custom_principals_only

Flake rate in main: 25.00% (Passed 24 times, Failed 8 times)

Stack Traces | 0.03s run time
Failed
github.com/dadrus/heimdall/cmd/validate::TestValidateRuleset/using_http_scheme_for_upstream_communication

Flake rate in main: 29.17% (Passed 51 times, Failed 21 times)

Stack Traces | 0.03s run time
Failed
github.com/dadrus/heimdall/internal/config::TestNewConfigurationFromStructWithDefaultsOnly

Flake rate in main: 29.17% (Passed 51 times, Failed 21 times)

Stack Traces | 0s run time
Failed
github.com/dadrus/heimdall/internal/config::TestNewConfigurationWithConfigFile

Flake rate in main: 29.17% (Passed 51 times, Failed 21 times)

Stack Traces | 0.04s run time
Failed

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

@dadrus dadrus changed the title feat: jwt and oauth2_introspection authenticators updated to support DPoP wip: jwt and oauth2_introspection authenticators updated to support DPoP Apr 30, 2026
@dadrus dadrus marked this pull request as draft April 30, 2026 05:49
@dadrus

dadrus commented May 8, 2026

Copy link
Copy Markdown
Owner Author

Since there is a need for a master secret and the current secret management implementation is kind of cumbersome, I decided to refactor that first, which resulted in #3238. As soon as that is done, the work on this PR will continue

return s
}

func (s *demonstratingPoPStrategy) Assert(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🚫 [golangci] reported by reviewdog 🐶
calculated cyclomatic complexity for function Assert is 15, max is 11 (cyclop)


func (b binder) Binding() [32]byte { return b }

type nonceKey nonce.Key

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🚫 [golangci] reported by reviewdog 🐶
type nonceKey is unused (unused)


type nonceKey nonce.Key

func (k nonceKey) NonceKey() nonce.Key { return nonce.Key(k) }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🚫 [golangci] reported by reviewdog 🐶
func nonceKey.NonceKey is unused (unused)

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.

Support for DPoP (RFC 9449)

1 participant