atproto/browser/oauth/flow
Async mirror of atproto/oauth/flow.gleam: start resolves the account
natively, discovers and verifies its authorization server, and pushes
the request (PAR) with PKCE and DPoP; callback checks the redirect
(state, iss, error), exchanges the code, and yields a session whose
sub was verified against the issuer.
A thin async wrapper over atproto_core/oauth/flow: it generates the
browser one-shots (WebCrypto PKCE, WebCrypto DPoP key, state) and pairs
the kernel’s pending flow with the key the kernel never sees. Meant for
a public client.
Surviving the redirect: a Flow is not plain data. Its DpopKey is a
non-extractable WebCrypto CryptoKeyPair, which cannot be written to
sessionStorage or JSON at all, and its Gleam values do not survive a
structured clone. Store the two halves separately before navigating:
to_json(flow) in sessionStorage under state(flow), and
dpop_key(flow) in IndexedDB (structured clone keeps a CryptoKeyPair
usable and still non-extractable). On the callback page, take (read and
delete) both by the returned state and resume before callback. A
flow is single-use; deleting the stored halves is what enforces that.
Types
A completed login: the verified session and the DPoP key its tokens are bound to. Keep the key in IndexedDB alongside the session.
pub type Authenticated {
Authenticated(
session: session.VerifiedSession,
dpop_key: dpop.DpopKey,
)
}
Constructors
-
Authenticated( session: session.VerifiedSession, dpop_key: dpop.DpopKey, )
pub type CallbackError =
flow.CallbackError
pub type ClientAuth =
transport.ClientAuth
Flow
opaquepub opaque type Flow
pub type FlowError =
flow.StartError
pub type ResumeError =
flow.ResumeError
Values
pub fn callback(
client: xrpc.Client,
flow flow: Flow,
query query: List(#(String, String)),
client_auth client_auth: transport.ClientAuth,
) -> promise.Promise(Result(Authenticated, flow.CallbackError))
Complete the flow from the redirect’s query parameters, all of them as
received (e.g. uri.parse_query of window.location.search).
pub fn callback_error_to_string(
error: flow.CallbackError,
) -> String
pub fn dpop_key(flow: Flow) -> dpop.DpopKey
The flow’s key half, for IndexedDB. See the module docs.
pub fn flow_error_to_string(error: flow.StartError) -> String
pub fn issuer(flow: Flow) -> remote.Origin
pub fn pds(flow: Flow) -> remote.Origin
pub fn resume(
client: xrpc.Client,
stored stored: String,
dpop_key dpop_key: dpop.DpopKey,
) -> promise.Promise(Result(Flow, flow.ResumeError))
Rebuild a flow from to_json output and its stored DPoP key,
re-verifying the issuer’s metadata.
pub fn resume_error_to_string(error: flow.ResumeError) -> String
pub fn resume_with(
client: xrpc.Client,
stored stored: String,
dpop_key dpop_key: dpop.DpopKey,
policy policy: remote.Policy,
plc_directory plc_directory: remote.Origin,
) -> promise.Promise(Result(Flow, flow.ResumeError))
resume for a flow started with start_with: the stored origins are
parsed under policy, and plc_directory must be the one the flow was
started with. Local development only.
pub fn start(
client: xrpc.Client,
resolver resolver: String,
identifier identifier: String,
client_id client_id: String,
redirect_uri redirect_uri: String,
scope scope: String,
client_auth client_auth: transport.ClientAuth,
) -> promise.Promise(Result(#(String, Flow), flow.StartError))
Returns the authorization-server URL to send the user to, and the pending flow to keep until the callback.
pub fn start_with(
client: xrpc.Client,
resolver resolver: String,
identifier identifier: String,
client_id client_id: String,
redirect_uri redirect_uri: String,
scope scope: String,
client_auth client_auth: transport.ClientAuth,
plc_directory plc_directory: remote.Origin,
) -> promise.Promise(Result(#(String, Flow), flow.StartError))
start with DID documents taken from plc_directory, for local
development against a devnet. Parse the directory with
remote.parse_origin_with(_, remote.AllowLocalDevelopment); its policy
then governs the whole flow, and the resulting Flow, session, refresh
and revoke inherit it. Never use this in production.
pub fn state(flow: Flow) -> String
The state this flow was started with: the key to store it under.