atproto_oauth
Provides tools to handle OAuth for AT Protocol and Bluesky Social.
0.8.0
2.7K downloads/mo
#4823 most downloaded on pub.dev
myConsciousness/atproto.dart
What this package is like to depend on
Last release 15 days ago
08 Aug 2026
Release timing varies
gaps range from 9 days to 13 months
Nearly every release is documented
notes for 15 of 15 stable releases
Nothing withdrawn
no release was ever pulled
3 years old
16 releases · first in 2023
10 releases in the last 12 months
see the full history below
Release timeline
16 releases · Nov 2023 to Aug 2026
2024
2025
2026
Releases
latest 16-
0.8.008 Aug 2026Release notes
Open source →- feat:
revokenow actually revokes at the authorization server. It was local-only — the session was dropped from theOAuthSessionStoreand nothing was sent — so after a "log out" the refresh token stayed live at the server for its full lifetime, and anyone holding a copy (a leaked backup, a shared device, a compromised store) could keep minting access tokens. When the server publishes an RFC 7009revocation_endpoint, the refresh token is now POSTed to it with a DPoP proof before the local delete (the refresh token in preference to the access token, since revoking it takes the whole grant down). The call is best-effort: a server that publishes no endpoint is skipped, and a network or server failure never blocks the logout. - feat:
OAuthServerMetadataparsesrevocation_endpoint, and it is held to the samehttps+ same-origin-as-issuer check already applied to the PAR, authorization and token endpoints. - feat:
OAuthSessionManageraccepts atimeout(default 30s,defaultOAuthSessionTimeout) bounding a single restore or refresh round trip. Every request on a manager queues behind the restore/refresh single flight, so an unbounded token call — a hung server, a stalled connection, an injected client whose I/O never completes — held all of them indefinitely with no way out.atproto_coreapplies the same bound to the legacy (non-OAuth) refresh callback; the OAuth path now matches.ATProto.fromOAuthSession,Bluesky.fromOAuthSession,BlueskyChat.fromOAuthSessionandOzoneTool.fromOAuthSessionpass their owntimeoutthrough. - fix: a refresh that gets
invalid_grantfor a token the store has already rotated past now returns the current stored session instead of throwingOAuthSessionRevokedException. v0.5.0 stopped such a stale failure from deleting the newer session, but still reported it as revoked — so the caller logged the user out of an account that held a perfectly valid session, undoing the very thing the delete-guard protected. The exception is now raised only when the session that actually failed is the one still stored.
- feat:
-
0.7.108 Aug 2026Release notes
Open source →- security: the metadata, PAR and token requests no longer follow HTTP redirects. v0.7.0 host-checks the authorization-server origin, but a
3xxfrom it would let the server pivot the follow-up (a DPoP-signed PAR/token POST) onto another host, re-opening that SSRF. A3xxnow surfaces as a response the caller rejects, or — for metadata discovery — falls back from, rather than being chased.
- security: the metadata, PAR and token requests no longer follow HTTP redirects. v0.7.0 host-checks the authorization-server origin, but a
-
0.7.008 Aug 2026Release notes
Open source →- security: the authorization server discovered from a PDS's
oauth-protected-resourcemetadata (RFC 9728) is now held to the same SSRF host policy the identity resolver applies to the PDS itself. The PDS host was validated, but theauthorization_serversentry — attacker-influenced, since it comes from a PDS reached via an attacker-supplied handle — was taken verbatim (onlyisAbsolutewas checked). An entry such ashttps://10.0.0.5:9200orhttps://169.254.169.254would direct the client's DPoP-signed PAR/token requests at an internal host: a blind SSRF. The AS host is now rejected unless it is an https bare origin on a non-reserved host, mirroring the PDS check. - feat:
OAuthClientacceptsallowPrivateNetwork(defaultfalse), applied to both the default identity resolver and the authorization-server host check, so a development deployment can opt private-network hosts back in.
- security: the authorization server discovered from a PDS's
-
0.6.026 Jul 2026Release notes
Open source →- 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.
- fix:
-
0.5.116 Jul 2026 -
0.5.016 Jul 2026Release notes
Open source →- 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.
- feat!: OAuth processes are now fully pluggable — inject
-
0.4.013 Jul 2026Release notes
Open source →- 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).
- fix!: the
-
0.3.110 Jul 2026Release notes
Open source →- fix: generate the PKCE
code_verifierand OAuthstatewithRandom.secure()instead of the non-cryptographicRandom(). - fix: remove bias in the DPoP key-generation seed (
nextInt(256)).
- fix: generate the PKCE
-
0.3.020 May 2026 -
0.2.020 Feb 2026 -
0.1.230 Jul 2025 -
0.1.118 Jul 2025 -
0.1.1-legacy28 Jul 2025 pre-releaseNothing published for this version
-
0.1.019 Nov 2024 -
0.0.116 Nov 2024 -
0.0.005 Nov 2023