atproto-oauth
Installation
SKILL.md
AT Protocol OAuth
Guidance for implementing the AT Protocol OAuth 2.1 profile: PAR, DPoP-bound tokens, PKCE, client metadata documents, scopes/permission sets, identity verification, and session/security hardening in TypeScript.
Protocol Essentials
AT Proto OAuth is OAuth 2.1 with no opt-outs on the following:
- PKCE S256 is mandatory.
code_challenge_method=S256only — neverplain. Verifier is 43–128 random chars from[A-Z a-z 0-9 - . _ ~]. - PAR (Pushed Authorization Request) is mandatory. The browser never sees the real authorization parameters — they're POSTed server-to-server to
pushed_authorization_request_endpointfirst, returning arequest_urithat's the only thing placed on the authorize redirect. - DPoP-bound tokens are mandatory. Every request to the authorization server (AS) and resource server (PDS) carries a signed DPoP proof JWT (RFC 9449).
dpop_bound_access_tokens: trueis required in client metadata. There is no bearer-only mode. client_idis a URL, not an opaque string. It resolves to a JSON client metadata document that the AS fetches at the start of every flow — this is AT Proto's dynamic client registration mechanism, replacing static pre-registration. The exact sameclient_idstring must appear byte-for-byte across metadata publication, PAR, authorize, and token/refresh calls — any drift invalidates the grant.- No
client_secret, ever. Confidential clients (anything with a server-side component) authenticate to the token endpoint with aprivate_key_jwtclient assertion (JWT signed ES256, referencing a key published in the client'sjwks/jwks_uri). Public clients (pure browser SPA, native apps with no backend) authenticate with DPoP proof possession alone —token_endpoint_auth_method: none.
Session lifetime differs by client type: confidential clients get refresh tokens valid up to 180 days (session itself unlimited, rotates keys periodically); public clients are capped at 14 days total, silently, until day 15 when refresh suddenly starts failing with invalid_grant.