Skip to content

v0.25.0

Release Date: 2026-10-27

Relying parties can call SystemAuth's APIs, such as the managed-principal API, with their signed-in people's DPoP-bound session tokens. SystemAuth accepts a bound token only with a proof by the key it is bound to, and until now an application had no public way to add one, so mpclient and proxies that sent the token as Bearer were refused. The new relyingparty.Client.Transport adds the proofs. It adds no breaking changes and no migrations.

Highlights

  • Session tokens to SystemAuth's APIs. relyingparty.Client.Transport is an http.RoundTripper for requests that carry a token the relying party obtained. The caller sets it in Authorization, as Bearer or DPoP. The transport presents a token bound to the client's key with the DPoP scheme and a new proof for the request's method and URL, and an unbound token (from a client registered with allow_bearer_tokens) unchanged as Bearer.
  • mpclient works with bound tokens. Give it an http.Client with the transport. Its calls are unchanged: managers create, list and sign in managed principals with their session token.
  • Server nonces. When SystemAuth asks for a nonce (dpop.require_nonce), the transport retries once with it if the request body can be sent again, and otherwise keeps the nonce for the next request, as a reverse proxy's streamed body needs.
  • Another key's token stays unusable. A JWT bound to a key the relying party does not hold, such as a caller's own token, is refused with ErrForeignDPoPToken before anything is sent. An opaque token's binding cannot be read locally, so it is sent with the relying party's proof, and SystemAuth refuses it if it is bound to another key.
  • Proofs only for SystemAuth's tokens. The transport reads a JWT's binding only once its signature verifies against SystemAuth's keys, so it never makes a proof for a token someone made up; such a token is refused with ErrInvalidToken. Its expiry is left to SystemAuth, which answers an expired token as before.

Added

  • relyingparty.Client.Transport(base http.RoundTripper) http.RoundTripper.

Changed

  • The relying party's own calls to SystemAuth's userinfo, memberships and access-changes endpoints go through Transport, so they share its nonce handling. A JWT access token is now sent only once its signature verifies against SystemAuth's keys, which the client already fetched to verify ID tokens; the client's own session tokens always do.

Documentation

Using it

  1. Build the managed-principal client with the relying party's transport:

    mp := mpclient.New(issuer+"/managed-principals", &http.Client{Transport: rp.Transport(nil)})
    
  2. A reverse proxy that forwards a person's calls to SystemAuth's APIs sets Authorization: Bearer <session token> as before and uses rp.Transport(base) as its transport.

  3. Send these requests to the URL SystemAuth checks proofs against: its issuer origin, or dpop.resource_base_url when it sets one. A proof's htu is the request's URL, so a request to another origin, such as an internal address, does not verify.

Nothing changes for existing deployments.

For release history, see the changelog.