Documentation
¶
Overview ¶
Package oauth implements the OAuth authorization-code flow with PKCE for remote providers that support it (currently Anthropic). It provides the PKCE primitives, the loopback callback server, and the per-provider flow driver (authorize URL, token exchange, refresh, identity bootstrap).
Index ¶
- Constants
- func AuthorizeURL(def provider.Definition, challenge, state, redirectURI string) string
- func ExpiresAt(expiresIn int) time.Time
- func GenerateChallenge(verifier string) string
- func GenerateState() (string, error)
- func GenerateVerifier() (string, error)
- type CallbackResult
- type CallbackServer
- type DeviceResponse
- type Identity
- type TokenResponse
Constants ¶
const ChallengeMethod = "S256"
ChallengeMethod is the PKCE challenge method, always "S256".
const DeviceGrantType = "urn:ietf:params:oauth:grant-type:device_code"
DeviceGrantType is the RFC 8628 grant type for device-code token requests.
Variables ¶
This section is empty.
Functions ¶
func AuthorizeURL ¶
func AuthorizeURL(def provider.Definition, challenge, state, redirectURI string) string
AuthorizeURL builds the authorization-code flow URL for the user to open. The provider's AuthorizeURL, ClientID, Scopes, and AuthorizeParams are taken from def; challenge and state are generated by the caller (pkce.go).
func GenerateChallenge ¶
GenerateChallenge returns the S256 PKCE code challenge for verifier: BASE64URL(SHA256(verifier)). S256 is the only method Anthropic accepts.
func GenerateState ¶
GenerateState returns a random hex-encoded CSRF state parameter. The state ties the callback to the authorize request that started the flow; the callback server rejects any callback whose state does not match.
func GenerateVerifier ¶
GenerateVerifier returns a high-entropy PKCE code verifier: verifierLen random bytes, base64url-encoded (no padding). The verifier is sent in the token exchange; the challenge derived from it is sent in the authorize request.
Types ¶
type CallbackResult ¶
type CallbackResult struct {
Code string
State string
Err error // non-nil if the provider sent an error or state mismatched
}
CallbackResult is what the loopback server delivers when the OAuth provider redirects back. Code is the authorization code to exchange; State is the CSRF state the provider echoed back (must match what we sent).
type CallbackServer ¶
type CallbackServer struct {
// contains filtered or unexported fields
}
CallbackServer is a transient HTTP server that listens on localhost for the OAuth redirect. It exists for the duration of one authorization-code flow: Start it, open the authorize URL, wait on Result(), then it shuts down.
func NewCallbackServer ¶
func NewCallbackServer(port int, path, host, expectedState string) *CallbackServer
NewCallbackServer creates a server bound to localhost:port. expectedState is the CSRF state we sent in the authorize request; the callback is rejected if the provider's echo does not match. path is the URL path to register (e.g. "/callback", "/auth/callback"). host is the hostname used in the redirect URI ("localhost" or "127.0.0.1").
func (*CallbackServer) Port ¶
func (cs *CallbackServer) Port() int
Port returns the actual bound port (useful if Start picked a random one, though Anthropic does not allow port fallback).
func (*CallbackServer) Start ¶
func (cs *CallbackServer) Start() (string, error)
Start binds the listener and begins serving. It returns the full redirect URI the provider should use (http://localhost:{port}/callback).
func (*CallbackServer) Wait ¶
func (cs *CallbackServer) Wait(ctx context.Context) CallbackResult
Wait blocks until the callback is received or ctx is cancelled. The server is shut down before returning.
type DeviceResponse ¶
type DeviceResponse struct {
DeviceCode string `json:"device_code"`
UserCode string `json:"user_code"`
VerificationURI string `json:"verification_uri"`
VerificationURIComplete string `json:"verification_uri_complete"`
Interval int `json:"interval"`
ExpiresIn int `json:"expires_in"`
}
DeviceResponse is the result of a device authorization request (RFC 8628 §3.1).
func RequestDeviceCode ¶
func RequestDeviceCode(ctx context.Context, def provider.Definition) (DeviceResponse, error)
RequestDeviceCode starts a device-code flow by POSTing to the provider's device authorization endpoint. The returned DeviceResponse contains the user code and verification URI to display to the user.
type Identity ¶
Identity is the account/org info recovered after a token exchange.
func FetchIdentity ¶
func FetchIdentity(ctx context.Context, def provider.Definition, accessToken string) (Identity, error)
FetchIdentity recovers account info after a token exchange. It dispatches based on def.IdentityMethod: "bootstrap" calls Anthropic's claude_cli endpoint, "userinfo" calls a standard OIDC userinfo endpoint. Returns a zero Identity (no error) if IdentityURL is empty.
type TokenResponse ¶
type TokenResponse struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
ExpiresIn int `json:"expires_in"`
TokenType string `json:"token_type"`
Scope string `json:"scope"`
}
TokenResponse is the result of a token exchange or refresh. The fields map to standard OAuth 2.0 token endpoint responses.
func Exchange ¶
func Exchange(ctx context.Context, def provider.Definition, code, codeVerifier, redirectURI, state string) (TokenResponse, error)
Exchange trades an authorization code for access and refresh tokens. codeVerifier must match the challenge sent in AuthorizeURL. If def has a ClientSecret, it is included in the request (required by confidential clients like Google).
func PollDeviceToken ¶
func PollDeviceToken(ctx context.Context, def provider.Definition, dr DeviceResponse) (TokenResponse, error)
PollDeviceToken polls the token endpoint until the user authorizes the device, the device code expires, or ctx is cancelled. It returns the token response on success or an error on failure.
func Refresh ¶
func Refresh(ctx context.Context, def provider.Definition, refreshToken string) (TokenResponse, error)
Refresh exchanges a refresh token for a new access token. The new response may include a new refresh_token (rotation); the caller should persist it.