pub fn client_assertion(
key: &SigningKey,
client_id: &str,
issuer: &str,
now: i64,
) -> Result<String>Expand description
Mint a private_key_jwt client assertion.
aud is the issuer, not the token endpoint. RFC 7523 permits either
reading; atproto settles it — “the aud claim of the client assertion JWT
must be the Authorization Server’s issuer” — which also means one audience
covers PAR, token, refresh and revocation alike.
A fresh jti per call, because that claim exists to stop replay.
But NOT per retry. The nonce-challenge retry in
super::request::send_with_dpop re-sends the identical form body and
re-mints only the DPoP proof, so the assertion — and its jti — is replayed
verbatim. This matches the reference client, which does the same, but an
authorization server that enforces single-use jti on client assertions
would answer the retry with invalid_client and turn a recoverable nonce
challenge into a failed login.
This comment previously claimed the opposite. It was wrong, and the retry path is structurally unable to reach the assertion builder.
The header carries kid and nothing else beyond alg (which
super::jwt::sign owns). The reference sends no typ here, and keeping it
absent leaves typ meaningful where it IS load-bearing — dpop+jwt on a
DPoP proof. nbf is not required and is not sent.