Skip to content

DPoP htu validation fails when client sends full URL per RFC 9449 #3216

@marythought

Description

@marythought

Summary

The DPoP htu claim validation has a mismatch between what SDKs send and what the server accepts. Per RFC 9449, htu should be the full HTTP target URI (scheme + host + path, without query/fragment). URLs should follow RFC 3986 §6.2.3 normalization — scheme-default ports must be stripped (no :443 for https, no :80 for http) and empty paths normalized to /.

Currently:

  • The Java SDK sends the full URL (e.g., https://platform.example.com/policy.namespaces.NamespaceService/GetNamespace) — correct per RFC
  • The Go SDK sends only the bare procedure path (e.g., /policy.namespaces.NamespaceService/GetNamespace) — needs to include scheme+host
  • The server only matches against the bare procedure path in the ConnectRPC interceptor — needs to also accept the full URL form

This surfaces in reverse-proxy deployments where the Java SDK's full-URL htu doesn't match the server's path-only expectation.

Current behavior

  • receiverInfo.u starts with only the bare procedure path
  • lookupGatewayPaths can add origin-prefixed aliases, but only for:
    • /kas.AccessService/Rewrap/kas/v2/rewrap
    • Other procedures only if a Pattern header is present (grpc-gateway case)
  • For ConnectRPC requests through a reverse proxy, no Origin/Grpcgateway-Origin headers are present, so lookupOrigins returns nothing and no full-URL aliases are added
  • KAS already accepts hosts via an alias list to support matching the KAO url field, but currently only uses public_hostname from opentdf.yaml

Expected behavior

  1. Server should accept htu as either the bare procedure path (backwards compat) or the full origin + procedure URL
  2. Go SDK should include scheme+host in htu per RFC 9449
  3. All URL comparisons should apply RFC 3986 §6.2.3 normalization (strip default ports, normalize empty paths)

Proposed approach

Server-side

  1. Add x-forwarded-host to the list of headers checked by lookupOrigins — this supports non-gateway reverse proxy requests with standard load balancer configuration
  2. Add a hostnames (or similar) field to opentdf.yaml — a configurable list of accepted hostnames for htu matching. These would either:
    • Allowlist proxy-supplied header values (when a proxy is configured), or
    • Be applied directly as valid origins (when no proxy is configured, and optionally behind a flag)
  3. Wire the configured hostnames into KAS — possibly as two separate but related lists for KAO url matching and DPoP htu matching, appended alongside the proxy-supplied (grpcgateway-origin) and browser-supplied (origin) values

SDK-side

  1. Go SDK — include scheme+host in the htu claim
  2. All SDKs — ensure default ports are stripped per RFC 3986 §6.2.3 normalization

Related

Relevant code

  • Server: authn.goreceiverInfo construction (~L332), lookupGatewayPaths (~L710), lookupOrigins (~L689), validateDPoP htu matching (~L652)
  • Go SDK: token_adding_interceptor.goGetDPoPToken sets htu to bare path (~L153)
  • Java SDK: AuthInterceptor.kt — uses full request.url for htu

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions