Documentation
¶
Overview ¶
Package oauth implements the ATProto OAuth 2.0 client with mandatory PKCE, PAR (Pushed Authorization Requests), and DPoP (Demonstration of Proof-of-Possession).
Index ¶
- Variables
- func CreateDPoPProof(key *crypto.P256PrivateKey, method, targetURL, nonce, accessToken string) (string, error)
- func RevokeToken(ctx context.Context, endpoint, token string, auth ClientAuth, ...)
- type AuthServerMetadata
- type AuthState
- type AuthorizeOptions
- type AuthorizeResult
- type CallbackParams
- type Client
- func (c *Client) AuthenticatedClient(ctx context.Context, did string) (*xrpc.Client, error)
- func (c *Client) Authorize(ctx context.Context, opts AuthorizeOptions) (*AuthorizeResult, error)
- func (c *Client) Callback(ctx context.Context, params CallbackParams) (*Session, error)
- func (c *Client) SignOut(ctx context.Context, did string) error
- type ClientAuth
- type ClientMetadata
- type ConfidentialClientAuth
- type ECPublicJWK
- type ExchangeCodeConfig
- type JWKSet
- type MemorySessionStore
- type MemoryStateStore
- type NonceStore
- type OAuthError
- type PKCEChallenge
- type ProtectedResourceMetadata
- type PublicClientAuth
- type RefreshTokenConfig
- type Session
- type SessionStore
- type StateStore
- type StaticTokenSource
- type TokenSet
- type TokenSource
- type Transport
Constants ¶
This section is empty.
Variables ¶
var ( // ErrInvalidState is returned when the callback state parameter doesn't // match a pending authorization request. ErrInvalidState = errors.New("oauth: invalid or expired state parameter") // ErrIssuerMismatch is returned when the callback iss parameter doesn't // match the expected authorization server. ErrIssuerMismatch = errors.New("oauth: issuer mismatch") // ErrMissingIssuer is returned when the callback is missing the required // iss parameter and the AS declared support for it. ErrMissingIssuer = errors.New("oauth: missing iss parameter in callback") // ErrIssuerVerification is returned when the sub DID's resolved PDS does // not point to the expected authorization server. ErrIssuerVerification = errors.New("oauth: issuer verification failed — DID does not resolve to expected AS") // ErrMissingScope is returned when the token response is missing the // required "atproto" scope. ErrMissingScope = errors.New("oauth: token response missing required atproto scope") // ErrNoSession is returned when no session exists for the requested DID. ErrNoSession = errors.New("oauth: no session for DID") // ErrTokenExpired is returned when a token has expired and cannot be refreshed. ErrTokenExpired = errors.New("oauth: token expired and no refresh token available") // ErrNoRefreshToken is returned when refresh is attempted without a refresh token. ErrNoRefreshToken = errors.New("oauth: no refresh token available") // ErrUseDPoPNonce is the error code returned by servers requiring a DPoP nonce. ErrUseDPoPNonce = errors.New("oauth: use_dpop_nonce") )
Functions ¶
func CreateDPoPProof ¶
func CreateDPoPProof(key *crypto.P256PrivateKey, method, targetURL, nonce, accessToken string) (string, error)
CreateDPoPProof creates a signed DPoP proof JWT per RFC 9449.
func RevokeToken ¶
func RevokeToken(ctx context.Context, endpoint, token string, auth ClientAuth, dpopKey *crypto.P256PrivateKey, nonces *NonceStore, httpClient *http.Client)
RevokeToken revokes a token. Errors are silently ignored per spec.
Types ¶
type AuthServerMetadata ¶
type AuthServerMetadata struct {
Issuer string `json:"issuer"`
AuthorizationEndpoint string `json:"authorization_endpoint"`
TokenEndpoint string `json:"token_endpoint"`
PushedAuthorizationRequestEndpoint string `json:"pushed_authorization_request_endpoint"`
RevocationEndpoint string `json:"revocation_endpoint,omitempty"`
DPoPSigningAlgValuesSupported []string `json:"dpop_signing_alg_values_supported"`
ScopesSupported []string `json:"scopes_supported"`
ResponseTypesSupported []string `json:"response_types_supported"`
GrantTypesSupported []string `json:"grant_types_supported"`
CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported"`
TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported"`
AuthorizationResponseIssParameterSupported bool `json:"authorization_response_iss_parameter_supported"`
RequirePushedAuthorizationRequests bool `json:"require_pushed_authorization_requests"`
ClientIDMetadataDocumentSupported bool `json:"client_id_metadata_document_supported"`
ProtectedResources []string `json:"protected_resources,omitempty"` // RFC 9728 §4
}
AuthServerMetadata is the OAuth 2.0 Authorization Server Metadata per RFC 8414, with ATProto-specific requirements.
func FetchAuthServerMetadata ¶
func FetchAuthServerMetadata(ctx context.Context, client *http.Client, issuer string) (*AuthServerMetadata, error)
FetchAuthServerMetadata fetches the authorization server metadata. The issuer should be the AS origin (e.g., "https://bsky.social"). No HTTP redirects are followed (SSRF prevention).
type AuthState ¶
type AuthState struct {
Issuer string
DPoPKey *crypto.P256PrivateKey
AuthMethod string // "none" or "private_key_jwt"
Verifier string // PKCE verifier
RedirectURI string
AppState string // Opaque application state
TokenEndpoint string
RevocationEndpoint string
}
AuthState stores the pending authorization flow state.
func (*AuthState) MarshalJSON ¶
func (*AuthState) UnmarshalJSON ¶
type AuthorizeOptions ¶
type AuthorizeOptions struct {
// Input is the user's handle or DID.
Input string
// RedirectURI is the callback URL. Must be in ClientMetadata.RedirectURIs.
RedirectURI string
// Scope overrides the scope from ClientMetadata. Optional.
Scope string
// State is opaque application state passed through the flow. Optional.
State string
}
AuthorizeOptions configures an authorization request.
type AuthorizeResult ¶
AuthorizeResult is returned by Authorize with the URL to redirect the user to.
type CallbackParams ¶
type CallbackParams struct {
Code string
State string
Iss string // Authorization server issuer (for mix-up prevention)
}
CallbackParams are the parameters received from the OAuth callback.
type Client ¶
type Client struct {
// ClientMetadata is this client's metadata document.
ClientMetadata ClientMetadata
// Identity resolves DIDs and handles.
Identity *identity.Directory
// HTTPClient is the HTTP client for OAuth requests.
// None = default 30s timeout client.
HTTPClient gt.Option[*http.Client]
// SessionStore persists authenticated sessions. Required.
//
// Implementors are responsible for encrypting tokens at rest.
// The Session struct contains access and refresh tokens in plaintext.
SessionStore SessionStore
// StateStore stores pending authorization flow state. Required.
StateStore StateStore
// Key is the signing key for confidential clients.
// Nil for public clients.
Key *crypto.P256PrivateKey
// KeyID is the key identifier for confidential client assertions.
KeyID string
// contains filtered or unexported fields
}
Client is an ATProto OAuth 2.0 client that handles the complete authorization flow including PKCE, PAR, and DPoP.
func (*Client) AuthenticatedClient ¶
AuthenticatedClient returns an *xrpc.Client configured with DPoP authentication for the given user DID. The client is long-lived: it automatically refreshes stale tokens on each request, protected by a mutex so concurrent requests coalesce on a single refresh.
func (*Client) Authorize ¶
func (c *Client) Authorize(ctx context.Context, opts AuthorizeOptions) (*AuthorizeResult, error)
Authorize initiates the OAuth authorization flow. Returns the URL to redirect the user's browser to.
type ClientAuth ¶
type ClientAuth interface {
// Apply adds authentication parameters to the form values.
Apply(params url.Values, issuer string) error
}
ClientAuth adds client authentication parameters to token endpoint requests.
type ClientMetadata ¶
type ClientMetadata struct {
ClientID string `json:"client_id"`
ApplicationType string `json:"application_type,omitempty"`
GrantTypes []string `json:"grant_types"`
Scope string `json:"scope"`
ResponseTypes []string `json:"response_types"`
RedirectURIs []string `json:"redirect_uris"`
DPoPBoundAccessTokens bool `json:"dpop_bound_access_tokens"`
TokenEndpointAuthMethod string `json:"token_endpoint_auth_method"`
TokenEndpointAuthSigningAlg string `json:"token_endpoint_auth_signing_alg,omitempty"`
JWKS *JWKSet `json:"jwks,omitempty"`
ClientName string `json:"client_name,omitempty"`
ClientURI string `json:"client_uri,omitempty"`
LogoURI string `json:"logo_uri,omitempty"`
TOSURI string `json:"tos_uri,omitempty"`
PolicyURI string `json:"policy_uri,omitempty"`
}
ClientMetadata is the OAuth client metadata document. The client_id is the URL where this document is hosted.
type ConfidentialClientAuth ¶
type ConfidentialClientAuth struct {
ClientID string
Key *crypto.P256PrivateKey
KeyID string
}
ConfidentialClientAuth authenticates confidential clients using private_key_jwt.
type ECPublicJWK ¶
type ECPublicJWK struct {
KTY string `json:"kty"`
CRV string `json:"crv"`
X string `json:"x"`
Y string `json:"y"`
}
ECPublicJWK is the JSON Web Key representation of a P-256 public key, used in DPoP proof JWT headers.
func PublicJWK ¶
func PublicJWK(pub *crypto.P256PublicKey) ECPublicJWK
PublicJWK returns the JWK representation of a P-256 public key.
type ExchangeCodeConfig ¶
type ExchangeCodeConfig struct {
TokenEndpoint string
RevocationEndpoint string
Code string
CodeVerifier string
RedirectURI string
ClientAuth ClientAuth
DPoPKey *crypto.P256PrivateKey
Nonces *NonceStore
HTTPClient *http.Client
}
ExchangeCodeConfig configures a token exchange request.
type JWKSet ¶
type JWKSet struct {
Keys []ECPublicJWK `json:"keys"`
}
JWKSet is a JSON Web Key Set containing public keys for confidential clients.
type MemorySessionStore ¶
type MemorySessionStore struct {
// contains filtered or unexported fields
}
MemorySessionStore is an in-memory SessionStore for testing. Not safe for concurrent use.
func NewMemorySessionStore ¶
func NewMemorySessionStore() *MemorySessionStore
NewMemorySessionStore creates an empty in-memory session store.
func (*MemorySessionStore) DeleteSession ¶
func (s *MemorySessionStore) DeleteSession(_ context.Context, did string) error
func (*MemorySessionStore) GetSession ¶
func (*MemorySessionStore) SetSession ¶
type MemoryStateStore ¶
type MemoryStateStore struct {
// contains filtered or unexported fields
}
MemoryStateStore is an in-memory StateStore for testing. Not safe for concurrent use.
func NewMemoryStateStore ¶
func NewMemoryStateStore() *MemoryStateStore
NewMemoryStateStore creates an empty in-memory state store.
func (*MemoryStateStore) DeleteState ¶
func (s *MemoryStateStore) DeleteState(_ context.Context, state string) error
type NonceStore ¶
type NonceStore struct {
// contains filtered or unexported fields
}
NonceStore stores DPoP nonces per server origin. Safe for concurrent use.
func NewNonceStore ¶
func NewNonceStore() *NonceStore
NewNonceStore creates a new empty nonce store.
func (*NonceStore) Get ¶
func (s *NonceStore) Get(origin string) string
Get returns the stored nonce for the given origin, or "".
func (*NonceStore) Set ¶
func (s *NonceStore) Set(origin string, nonce string)
Set stores a nonce for the given origin.
type OAuthError ¶
type OAuthError struct {
Code string `json:"error"`
Description string `json:"error_description,omitempty"`
}
OAuthError represents an error response from an OAuth endpoint.
func (*OAuthError) Error ¶
func (e *OAuthError) Error() string
type PKCEChallenge ¶
type PKCEChallenge struct {
Verifier string // base64url-encoded 32 random bytes (43 chars)
Challenge string // base64url(SHA-256(Verifier))
Method string // "S256"
}
PKCEChallenge holds PKCE code challenge parameters for an authorization request.
func GeneratePKCE ¶
func GeneratePKCE() (*PKCEChallenge, error)
GeneratePKCE creates a new PKCE challenge with a cryptographically random verifier.
type ProtectedResourceMetadata ¶
type ProtectedResourceMetadata struct {
Resource string `json:"resource"`
AuthorizationServers []string `json:"authorization_servers"`
}
ProtectedResourceMetadata is the OAuth 2.0 Protected Resource Metadata per RFC 9728, used to discover the authorization server for a PDS.
func FetchProtectedResourceMetadata ¶
func FetchProtectedResourceMetadata(ctx context.Context, client *http.Client, pdsURL string) (*ProtectedResourceMetadata, error)
FetchProtectedResourceMetadata fetches the protected resource metadata from a PDS. The pdsURL should be the PDS origin (e.g., "https://bsky.social"). No HTTP redirects are followed (SSRF prevention).
type PublicClientAuth ¶
type PublicClientAuth struct {
ClientID string
}
PublicClientAuth authenticates public clients (token_endpoint_auth_method: "none").
type RefreshTokenConfig ¶
type RefreshTokenConfig struct {
TokenEndpoint string
RevocationEndpoint string
RefreshToken string
ClientAuth ClientAuth
DPoPKey *crypto.P256PrivateKey
Nonces *NonceStore
HTTPClient *http.Client
}
RefreshTokenConfig configures a token refresh request.
type Session ¶
type Session struct {
DPoPKey *crypto.P256PrivateKey
TokenSet TokenSet
}
Session represents an authenticated OAuth session for a user.
func (*Session) MarshalJSON ¶
func (*Session) UnmarshalJSON ¶
type SessionStore ¶
type SessionStore interface {
// GetSession retrieves a session by DID. Returns [ErrNoSession] if not found.
GetSession(ctx context.Context, did string) (*Session, error)
// SetSession stores or replaces a session for the given DID.
SetSession(ctx context.Context, did string, session *Session) error
// DeleteSession removes the session for the given DID.
DeleteSession(ctx context.Context, did string) error
}
SessionStore persists OAuth sessions. Keyed by user DID.
type StateStore ¶
type StateStore interface {
// GetState retrieves pending auth state. Returns [ErrInvalidState] if not found.
GetState(ctx context.Context, state string) (*AuthState, error)
// SetState stores pending auth state for the given state parameter.
SetState(ctx context.Context, state string, data *AuthState) error
// DeleteState removes the pending auth state.
DeleteState(ctx context.Context, state string) error
}
StateStore stores pending authorization flow state. Keyed by state parameter. Entries should be short-lived (auto-expire after ~10 minutes).
type StaticTokenSource ¶
type StaticTokenSource struct {
AccessToken string
Key *crypto.P256PrivateKey
}
StaticTokenSource is a TokenSource that always returns the same token. Useful for tests and short-lived operations.
func (*StaticTokenSource) Token ¶
func (s *StaticTokenSource) Token(_ context.Context) (string, *crypto.P256PrivateKey, error)
type TokenSet ¶
type TokenSet struct {
Issuer string `json:"iss"`
Sub string `json:"sub"`
Aud string `json:"aud"`
Scope string `json:"scope"`
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
ExpiresAt time.Time `json:"expires_at"`
RefreshDeadline time.Time `json:"refresh_deadline"` // ExpiresAt minus random jitter; refresh when Now > this
RefreshToken string `json:"refresh_token,omitempty"`
TokenEndpoint string `json:"token_endpoint"`
RevocationEndpoint string `json:"revocation_endpoint,omitempty"`
}
TokenSet holds the tokens from an OAuth token response.
func ExchangeCode ¶
func ExchangeCode(ctx context.Context, cfg *ExchangeCodeConfig) (*TokenSet, error)
ExchangeCode exchanges an authorization code for tokens.
func RefreshToken ¶
func RefreshToken(ctx context.Context, cfg *RefreshTokenConfig) (*TokenSet, error)
RefreshToken exchanges a refresh token for new tokens.
type TokenSource ¶
type TokenSource interface {
// Token returns the current access token and DPoP key.
// It may refresh the token if stale.
Token(ctx context.Context) (accessToken string, key *crypto.P256PrivateKey, err error)
}
TokenSource provides the current access token for DPoP-authenticated requests. Implementations must be safe for concurrent use.
type Transport ¶
type Transport struct {
// Base is the underlying transport. If nil, http.DefaultTransport is used.
Base http.RoundTripper
// Source provides the current access token and DPoP key.
Source TokenSource
// Nonces stores per-origin DPoP nonces.
Nonces *NonceStore
}
Transport is an http.RoundTripper that adds DPoP proof headers and handles nonce retry transparently. It uses a TokenSource to get the current (possibly refreshed) access token on each request.