Latch. Flow
(latch v0.3.0)
Copy Markdown
Drives the atproto OAuth flow against an authorization server: pushed authorization requests, the authorization redirect, token exchange, and refresh.
Requests to the authorization server are DPoP-bound. The server requires a
fresh DPoP nonce, so each request is attempted once without a nonce and
retried once with the none the server returns. The client assertion and
DPoP proof are regenreated on the retry, so neither jti is reused.
Summary
Functions
Builds the authorization redirect URL from a PAR request_uri.
Exchanges an authorization code for a session.
Performs a pushed authorization request, returning the request_uri.
Refreshes a session, returning a new session with rotated tokens.
Functions
@spec authorization_url(Latch.ServerMetadata.t(), String.t(), String.t()) :: String.t()
Builds the authorization redirect URL from a PAR request_uri.
The browser is sent here, per atproto only client_id and request_uri
travel in the URL, since the real parameters were pushed durig PAR.
@spec exchange_code( Latch.Config.t(), keyword() ) :: {:ok, Latch.Session.t()} | {:error, Latch.Error.InvalidResponse.t() | Latch.Error.MissingDPoPNonce.t() | Latch.Error.OAuth.t() | Latch.Error.SecurityViolation.t() | Latch.Error.Transport.t()}
Exchanges an authorization code for a session.
Verifies the token response sub matches expected_did (the DID resolved
before login) before building the session. The callback's state and iss
are validated upstream, before this is called.
Required options
:client_id,:client_jwk- the confidential client's id and signing key:redirect_uri- must match the value sent during PAR:code,:core_verifier- the authorization code and PKCE verifier:dpop_key- the per-session DPoP key:expected_did- the DID thesubmust match:pds_endpoint- the resource server, stored on the session
Optional
:now- base time forexpires_at(defaults to the current time)
@spec par(Latch.Config.t(), Latch.ServerMetadata.t(), keyword()) :: {:ok, String.t()} | {:error, Latch.Error.InvalidResponse.t() | Latch.Error.MissingDPoPNonce.t() | Latch.Error.OAuth.t() | Latch.Error.Transport.t()}
Performs a pushed authorization request, returning the request_uri.
Required options
:client_id, the client's id:redirect_uri,:scope,:state,:code_challenge- auth params:dpop_key- the per-session DPoP key
Optional
:login_hint- the user's handle or DID
@spec refresh( Latch.Config.t(), Latch.ServerMetadata.t(), Latch.Session.t(), keyword() ) :: {:ok, Latch.Session.t()} | {:error, Latch.Error.InvalidResponse.t() | Latch.Error.MissingDPoPNonce.t() | Latch.Error.OAuth.t() | Latch.Error.SecurityViolation.t() | Latch.Error.Transport.t()}
Refreshes a session, returning a new session with rotated tokens.
The refresh is DPoP-bound to the session's existing key (the tokens are
bound to it), and the new sub must still match the session's DID. Refresh
tokens are single-use, so the returned session carries the rotated tokens
and must replace the old one.
Required options
:client_id,:client_jwk- the confidential client's id and signing key
Optional
:now- base time forexpires_at(defaults to the current time)