DPoP proof JWTs (RFC 9449) for OpenFeed resource-server requests.
Every call to the sharing API carries two things: the access token in an
Authorization: DPoP <token> header, and a single-use proof in a DPoP
header, signed by the key OpenFeed has on file for your app. That is what
makes the token sender-constrained: stealing it is not enough, because the
thief cannot sign proofs.
Proofs are single-use. Never replay one — retrying a request means building a
fresh proof, which is why OpenFeed.Client handles its own retries instead of
delegating to Req's.
Claims
| Claim | Meaning |
|---|---|
jti | Unique per proof. The server rejects reuse. |
htm | HTTP method, uppercased. |
htu | Target URI, normalised, with query and fragment removed. |
iat | Issued at. |
exp | Short expiry (30s by default). |
nbf | Not before, back-dated by :clock_skew if set. |
ath | SHA-256 of the access token. Present on resource calls. |
nonce | Only when the server has demanded one via DPoP-Nonce. |
exp and nbf are not required by RFC 9449, but oidcc's own proofs include
them and they narrow the replay window, so we do too.
The algorithm is always PS256: OpenFeed's auth server hard-codes
dPoPSigningAlgValues = ['PS256'] and rejects ES256 and EdDSA outright, so
there is nothing to negotiate.
Summary
Functions
The ath claim value for an access token: base64url of its SHA-256, unpadded.
The htu claim value: the target URI with query and fragment removed.
Build a DPoP proof.
Functions
The ath claim value for an access token: base64url of its SHA-256, unpadded.
Returns nil for nil, so it can be threaded through unconditionally.
The htu claim value: the target URI with query and fragment removed.
Also normalises away a default port (:443 for https, :80 for http),
because the server reconstructs the URI from its own request and compares.
A mismatch on https://host:443/x versus https://host/x would otherwise
fail the proof for no good reason.
Examples
iex> OpenFeed.Dpop.htu("https://api.openfeed.au/v1/banking/accounts?limit=10#frag")
"https://api.openfeed.au/v1/banking/accounts"
iex> OpenFeed.Dpop.htu("https://api.openfeed.au:443/v1/app")
"https://api.openfeed.au/v1/app"
@spec proof(OpenFeed.Config.t(), atom() | String.t(), String.t(), keyword()) :: {:ok, String.t()} | {:error, term()}
Build a DPoP proof.
Options
:access_token— include theathclaim. Required by the resource server; omit only for proofs sent to the authorization server before a token exists.:nonce— the value from aDPoP-Nonceresponse header, when retrying after a nonce challenge.:lifetime— seconds untilexp. Defaults to30.:clock_skew— seconds to back-datenbfby. Defaults to0.
Examples
{:ok, proof} = OpenFeed.Dpop.proof(config, :get, url, access_token: token)