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
- Server should accept
htu as either the bare procedure path (backwards compat) or the full origin + procedure URL
- Go SDK should include scheme+host in
htu per RFC 9449
- All URL comparisons should apply RFC 3986 §6.2.3 normalization (strip default ports, normalize empty paths)
Proposed approach
Server-side
- Add
x-forwarded-host to the list of headers checked by lookupOrigins — this supports non-gateway reverse proxy requests with standard load balancer configuration
- 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)
- 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
- Go SDK — include scheme+host in the
htu claim
- All SDKs — ensure default ports are stripped per RFC 3986 §6.2.3 normalization
Related
Relevant code
- Server:
authn.go — receiverInfo construction (~L332), lookupGatewayPaths (~L710), lookupOrigins (~L689), validateDPoP htu matching (~L652)
- Go SDK:
token_adding_interceptor.go — GetDPoPToken sets htu to bare path (~L153)
- Java SDK:
AuthInterceptor.kt — uses full request.url for htu
Summary
The DPoP
htuclaim validation has a mismatch between what SDKs send and what the server accepts. Per RFC 9449,htushould 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:443for https, no:80for http) and empty paths normalized to/.Currently:
https://platform.example.com/policy.namespaces.NamespaceService/GetNamespace) — correct per RFC/policy.namespaces.NamespaceService/GetNamespace) — needs to include scheme+hostThis surfaces in reverse-proxy deployments where the Java SDK's full-URL
htudoesn't match the server's path-only expectation.Current behavior
receiverInfo.ustarts with only the bare procedure pathlookupGatewayPathscan add origin-prefixed aliases, but only for:/kas.AccessService/Rewrap→/kas/v2/rewrapPatternheader is present (grpc-gateway case)Origin/Grpcgateway-Originheaders are present, solookupOriginsreturns nothing and no full-URL aliases are addedpublic_hostnamefromopentdf.yamlExpected behavior
htuas either the bare procedure path (backwards compat) or the full origin + procedure URLhtuper RFC 9449Proposed approach
Server-side
x-forwarded-hostto the list of headers checked bylookupOrigins— this supports non-gateway reverse proxy requests with standard load balancer configurationhostnames(or similar) field toopentdf.yaml— a configurable list of accepted hostnames for htu matching. These would either:grpcgateway-origin) and browser-supplied (origin) valuesSDK-side
htuclaimRelated
Relevant code
authn.go—receiverInfoconstruction (~L332),lookupGatewayPaths(~L710),lookupOrigins(~L689),validateDPoPhtu matching (~L652)token_adding_interceptor.go—GetDPoPTokensets htu to bare path (~L153)AuthInterceptor.kt— uses fullrequest.urlfor htu