atproto_oauth 0.6.0
atproto_oauth: ^0.6.0 copied to clipboard
Provides tools to handle OAuth for AT Protocol and Bluesky Social.
Release Note #
0.6.0 #
- fix:
OAuthSessionManager.refreshOnUnauthorizedaccepts the access token the failed request actually carried, and skips the refresh when it has already been rotated past. The single flight only coalesces requests that overlap an in-progress refresh; a request already on the wire with the superseded token 401s afterwards, and each such response used to chain another rotation — spending an unused refresh token and emitting anonSessionUpdatedthe owner has to persist. A stale401is now simply retried with the current session.
0.5.1 #
- chore: bump
atproto_identityto^0.1.1.
0.5.0 #
- feat!: OAuth processes are now fully pluggable — inject
OAuthStateStore,OAuthSessionStore,DPoPNonceCache,IdentityResolver,DPoPSigner(in-memory/HTTP/pointycastle defaults provided). (#2060) - fix!: OAuth access/refresh tokens are treated as opaque — the library no longer decodes them as JWTs, fixing failures with PDS "light" access tokens that omit
aud. (#1982) - fix: concurrent refreshes are coalesced (single-flight by
sub) and aninvalid_grantno longer deletes a newer stored session, preventing spurious logouts under refresh-token rotation. - fix: the initial session restore in
OAuthSessionManageris single-flighted, so concurrent first requests no longer race into a double refresh that could surface a spuriousOAuthSessionRevokedExceptioneven though a valid session exists. - fix: token values are never included in exception messages.
- fix: a
200token/refresh response with an unparsable body no longer interpolates the raw body (which may carry liveaccess_token/refresh_tokenmaterial) into the exception message — only the content-type and body length are reported. - fix: the AS metadata
pushed_authorization_request_endpoint,authorization_endpoint, andtoken_endpointare now validated ashttpsand same-origin as the issuer, so a misconfigured or partially-compromised metadata document cannot redirect the code+verifier/PAR/refresh-token POSTs to anhttp://or off-origin host. - fix: the authorization
stateis consumed on every callback outcome (strict one-time use), and a malformed authorization context is rejected before the token exchange. - feat!:
OAuthSessionis now self-contained (issuer,pds,clientId,dpopPublicKey,dpopPrivateKey); DPoP nonces moved out of the session into a per-originDPoPNonceCache.fromJson/toJson;fromLegacyJson({required issuer, required pds})restores <=0.4.x payloads. - feat:
OAuthSessionManagerbuilds DPoP auth headers per request and refreshes tokens (single-flight, proactive + on-401);OAuthClientgainsrevoke/restore;callbackno longer needs anOAuthContextargument (stored viaOAuthStateStore, so callbacks survive app restarts); newOAuthSessionRevokedException. - feat!:
OAuthClient.authorize(identity)returns aUriand resolves the authorization server internally;resolveFromPds/resolveFromIdentityremoved.
0.4.0 #
- fix!: the
dpop-nonceresponse header is now treated as optional — a missing header (it is optional per RFC 9449) no longer crashes after a token has been successfully issued, which previously lost the token (O-1). - fix: the
use_dpop_nonceretry is now bounded (max 2) and the refresh path checks the status code first, preventing infinite recursion / stack overflow (O-2). - fix: the refresh token is preserved when the server does not rotate it, and
expires_indefaults gracefully (O-3). - fix: token/refresh responses are only JSON-parsed after the status is checked, so an HTML
502yields anOAuthExceptioninstead of aFormatException(O-4). - feat:
issvalidation (RFC 9207) in the callback, plus RFC 8414 server-metadata discovery, endpoint resolution, andsubDID validation (O-6/O-8). - feat:
OAuthSessiongainstoJson/fromJson,refresh()returns a new instance instead of mutating in place, and state comparison is constant-time (O-9). - fix: seed
FortunaRandomfromRandom.secure()(was a fixed, predictable seed) (O-7). - test: new test suite (PKCE, DPoP JWT, ES256 verify, nonce retry, iss/state) where there was previously none (O-11).
- feat!:
scopevalidation — a token response that omitsscope, or whose scope does not containatproto, is now rejected as required by the atproto OAuth profile. On refresh, a server that omitsscopefalls back to the session's already-validated scope. - feat: identity/authorization-server discovery —
OAuthClient.resolveFromPdsfetches the RFC 9728 protected-resource metadata from a PDS to find its authorization server, andOAuthClient.resolveFromIdentityresolves a handle or DID (handle→DID viacom.atproto.identity.resolveHandle, DID→DID document viaplc.directory/did:web), verifies the handle bidirectionally throughalsoKnownAs, extracts the#atproto_pdsendpoint, and pins the resolved DID asexpectedSub. The directservice-as-entryway constructor keeps working unchanged (O-8). - feat:
subaccount verification — when the account is known (fromresolveFromIdentity, or carried over on refresh), the token responsesubmust match it, otherwise anOAuthExceptionis thrown. - fix!: the callback now requires the
issparameter (RFC 9207) by default, not only when an explicitissuerargument is passed — atproto authorization servers always send it. - fix: the Pushed Authorization Request (PAR) is now sent with a DPoP proof and absorbs a
use_dpop_noncechallenge, so the nonce is pre-acquired and the same DPoP key is reused for the token request. - fix:
client_idis restricted tohttps://, with anhttp://exception only for loopback hosts (localhost/127.0.0.1/[::1]). - fix: the DPoP proof no longer carries a non-standard
subclaim (RFC 9449 defines onlyjti/htm/htu/iat, plus optionalnonce/ath).
0.3.1 #
- fix: generate the PKCE
code_verifierand OAuthstatewithRandom.secure()instead of the non-cryptographicRandom(). - fix: remove bias in the DPoP key-generation seed (
nextInt(256)).
0.1.2 #
- Fix SDK constraint to '">=3.8.0 <4.0.0"'.
0.1.1 #
- Bump SDK constraint to '^3.8.0'.
0.1.0 #
- Retry if a DPoP nonce error occurs during the execution of
refresh.
0.0.1 #
- First Release.
0.0.0 #
- Let's start.