Skip to content

DPoP on the server side

nate
Jul 9, 20262 min read1 read

DPoP on the server side

tproto OAuth requires DPoP (proof-of-possession tokens). the client side is well documented; these are the server-side mechanics, from zds, a PDS implementation in zig (src/atproto/oauth/dpop.zig).

nonces can be stateless

the server must issue and rotate DPoP nonces, but nothing requires storing them. zds derives the nonce as base64url(HMAC-SHA256(secret, big_endian(time / rotation_secs))) and accepts the counter−1 / counter / counter+1 windows on verification (dpop.zig:199-230). rotation and validation with zero storage; the only thing persisted is JTI replay protection (recordDpopJti(jti, expires_at)).

the use_dpop_nonce dance needs both channels

when a proof arrives without (or with a stale) nonce, respond with the fresh nonce in a DPoP-Nonce header and WWW-Authenticate: DPoP error="use_dpop_nonce" (dpop.zig:30-45) — real clients key their retry off the www-authenticate error code, and sending only the header strands them.

proof verification, field by field

  • htm — matches the http method
  • htu — matches publicUrl + path with query and fragment stripped before comparison (dpop.zig:183-197); proxies that rewrite paths break this first
  • ath — hash of the access token; must be present when a token is bound and absent when there is none (dpop.zig:127) — accepting a spurious ath is a spec violation that most test suites miss
  • jkt — the key thumbprint binding, computed per RFC 7638 with the canonical member order {crv, kty, x, y} (dpop.zig:169-180); any other member order produces a different thumbprint and every token check fails
  • the EC public key is reconstructed from the JWK's x/y with the parity byte taken from y[31] & 1 (dpop.zig:145-160)

sources

Did you enjoy this article?

Recommend it — Standard Reader surfaces well-loved writing to more readers across the network.

Across the AtmosphereDiscussions