# invalid_dpop_proof

The DPoP proof is missing or fails a validation check (RFC 9449 §4.3, §7.1).

## Meaning

At `/token` (`400`, OAuth error body), a `DPoP` header that is not a valid proof for the
request: too large or malformed, a `typ` other than `dpop+jwt`, an algorithm outside
ES256, PS256/384/512 and RS256/384/512, a missing or private `jwk`, an unsupported key, a
bad signature, missing claims, `htm` or `htu` not matching the request, an `iat` more
than 60 seconds old or more than 5 seconds ahead, or a reused `jti`. At `/userinfo` and
other protected resources (`401`, `WWW-Authenticate: DPoP`), also a bound token sent
without a proof, or a proof whose `ath` does not match the token.

## Impact

The request fails; the client gets no token or no claims. Mostly a client bug, a proxy
that changes the URL the client signs, or clock skew on the client.

## Diagnosis

1. At `/token` the refusal is recorded under `oauth.token.issued` (or
   `oauth.token.refreshed`) with reason `invalid_dpop_proof`; at a protected resource
   under `oauth.token.presented`. The cause names the check, such as `dpop_htu_mismatch`,
   `dpop_iat_too_old`, `dpop_iat_in_future`, `dpop_proof_replayed` or
   `dpop_ath_missing`, and a refused `iat` records `skew_seconds`.
2. For `dpop_htu_mismatch`, compare the `htu` with the issuer followed by the endpoint
   path, which is what the server checks.

## Mitigation

- As a client developer: create a new proof for every request, with `htm` and `htu` of
  that request, a fresh `jti` and the current time; at a resource include `ath`. See
  [DPoP](https://www.nauthera.io/docs/v1/tokens#dpop-sender-constrained-tokens).
- As an operator: if `skew_seconds` is large for every client, check the server's clock.
- As a user: tell the application's owner.

## Escalation

If a proof that satisfies RFC 9449 §4.3 is refused, open an issue with the request id,
the server version and the recorded cause. Never attach the proof or the token.

---

[OAuth error runbooks](https://www.nauthera.io/docs/v1/runbooks/oauth) · Generated from [`internal/runbook/oauth.yaml`](https://github.com/nauthera/nauthera-server/blob/main/internal/runbook/oauth.yaml) in nauthera-server.
