Documentation
¶
Index ¶
- Constants
- Variables
- func ApproveConsent(httpClient *http.Client) func(string) error
- func FollowRedirects(httpClient *http.Client) func(string) error
- func IsLocalhost(rawURL string) bool
- func LoopbackRedirectURI(addr net.Addr) string
- func MintClientAssertion(clientID, audience string, cfg ClientAssertionConfig) (string, error)
- func ValidateCIMDURL(rawURL string) error
- func ValidateHTTPS(meta *ASMetadata) error
- func ValidateIss(iss, expectedIssuer string, asAdvertisedSupport, strict bool) error
- type ASMetadata
- type ASMetadataStore
- type AuthClient
- func (c *AuthClient) ClientCredentials(ctx context.Context, req *ClientCredentialsRequest) (*ServerCredential, error)
- func (c *AuthClient) ClientCredentialsToken(clientID, clientSecret string, scopes []string) (*ServerCredential, error)deprecated
- func (c *AuthClient) ClientCredentialsTokenWithAssertion(clientID string, cfg ClientAssertionConfig, scopes []string) (*ServerCredential, error)deprecated
- func (c *AuthClient) DeviceAuthorization(ctx context.Context, req *DeviceAuthorizationRequest) (*DeviceAuthorizationResponse, error)
- func (c *AuthClient) GetCredential() (*ServerCredential, error)
- func (c *AuthClient) GetToken() (string, error)
- func (c *AuthClient) HTTPClient() *http.Client
- func (c *AuthClient) IsLoggedIn() bool
- func (c *AuthClient) JwtBearerGrant(ctx context.Context, req *JwtBearerGrantRequest) (*ServerCredential, error)
- func (c *AuthClient) Login(ctx context.Context, req *LoginRequest) (*ServerCredential, error)
- func (c *AuthClient) LoginWithBrowser(ctx context.Context, req *BrowserLoginRequest) (*ServerCredential, error)
- func (c *AuthClient) Logout() error
- func (c *AuthClient) PollDeviceToken(ctx context.Context, req *PollDeviceTokenRequest) (*ServerCredential, error)
- func (c *AuthClient) RefreshToken(ctx context.Context, req *RefreshTokenRequest) (*ServerCredential, error)
- func (c *AuthClient) ServerURL() string
- func (c *AuthClient) TokenExchange(ctx context.Context, req *TokenExchangeRequest) (*TokenExchangeResponse, error)
- type AuthTransport
- type AuthorizeError
- type BrowserLoginRequest
- type BrowserOpenError
- type CallbackError
- type CallbackParams
- type ClientAssertionConfig
- type ClientCredentialsRequest
- type ClientCredentialsSource
- type ClientOption
- func WithASMetadata(meta *ASMetadata) ClientOption
- func WithClientSecret(secret string) ClientOption
- func WithDPoPKey(key *DPoPKey) ClientOption
- func WithHTTPClient(client *http.Client) ClientOption
- func WithTokenEndpoint(path string) ClientOption
- func WithTransport(transport http.RoundTripper) ClientOption
- type ClientRegistrationRequest
- type ClientRegistrationResponse
- type CredentialStore
- type DPoPKey
- type DeleteRegistrationRequest
- type DeleteRegistrationResponse
- type DeviceAuthorizationRequest
- type DeviceAuthorizationResponse
- type DiscoveryOption
- type GetRegistrationRequest
- type GetRegistrationResponse
- type JwtBearerGrantRequest
- type LoginRequest
- type MemoryASMetadataStore
- type OAuth2TokenRequest
- type OAuth2TokenResponse
- type PollDeviceTokenRequest
- type ProactiveRefresher
- type RefreshTokenRequest
- type ScopeAwareTokenSource
- type ServerCredential
- type TokenEndpointAuthMethod
- type TokenError
- type TokenExchangeRequest
- type TokenExchangeResponse
- type TokenSource
- type UpdateRegistrationRequest
- type UpdateRegistrationResponse
Constants ¶
const DefaultASCacheTTL = 1 * time.Hour
DefaultASCacheTTL is the default time-to-live for cached AS metadata entries. AS metadata changes rarely (endpoint rotations, scope additions), so a 1-hour TTL balances freshness against redundant HTTP fetches.
const DefaultClientAssertionLifetime = 60 * time.Second
DefaultClientAssertionLifetime is the assertion lifetime applied when ClientAssertionConfig.Lifetime is zero. 60 seconds matches the short-lived recommendation in OIDC Core §9.
const DeviceCodeGrantType = "urn:ietf:params:oauth:grant-type:device_code"
DeviceCodeGrantType is the OAuth grant-type URI for the RFC 8628 device authorization grant. Exported so callers can advertise it, register it with discovery clients, or branch on it.
See: https://www.rfc-editor.org/rfc/rfc8628#section-3.4
const JwtBearerGrantType = "urn:ietf:params:oauth:grant-type:jwt-bearer"
JwtBearerGrantType is the RFC 7523 §2.1 grant_type value — the JWT bearer authorization grant. Distinct from the private_key_jwt CLIENT authentication method (which uses `client_assertion`); this grant exchanges a signed `assertion` for an access token.
const RefreshThreshold = 5 * time.Minute
RefreshThreshold is how long before expiry to proactively refresh
const TokenExchangeGrantType = "urn:ietf:params:oauth:grant-type:token-exchange"
TokenExchangeGrantType is the RFC 8693 grant_type value sent at the token endpoint to request a token exchange.
Variables ¶
var ( // ErrAuthorizationPending signals that the user has not yet completed // the verification step. Keep polling at the current interval. ErrAuthorizationPending = errors.New("RFC 8628 authorization_pending") // ErrSlowDown signals that the client polled faster than the AS-advised // interval. The caller MUST raise its interval by at least 5 seconds // (RFC 8628 §3.5) before the next poll. ErrSlowDown = errors.New("RFC 8628 slow_down") // ErrAccessDenied signals that the user explicitly rejected the // authorization. Terminal — do not poll further. ErrAccessDenied = errors.New("RFC 8628 access_denied") // ErrExpiredToken signals that the device_code's expires_in window // elapsed before the user approved. Terminal — restart the flow. ErrExpiredToken = errors.New("RFC 8628 expired_token") )
RFC 8628 §3.5 polling error codes mapped to typed sentinels. Use errors.Is to branch — the polling loop in `cmd/oneauth token device` is the canonical consumer:
- ErrAuthorizationPending: continue polling at the current interval.
- ErrSlowDown: bump the polling interval by 5s, then continue.
- ErrAccessDenied: terminal — user rejected. Surface to the user.
- ErrExpiredToken: terminal — code expired. Restart the flow.
var ErrIssMismatch = errors.New("RFC 9207 iss does not match authorization server issuer")
ErrIssMismatch is returned by ValidateIss when the RFC 9207 `iss` query parameter is present on an authorization response but does not byte-equal the expected authorization-server issuer identifier. A mismatch is the canonical mix-up-attack signal — a different AS is trying to convince the client to exchange a code against the wrong token endpoint. The flow MUST be aborted.
See: https://www.rfc-editor.org/rfc/rfc9207#section-2.4
var ErrIssMissing = errors.New("RFC 9207 iss missing from authorization response")
ErrIssMissing is returned by ValidateIss when the RFC 9207 `iss` query parameter is absent from the authorization response under conditions that require its presence — either because the AS metadata advertised support (so a missing iss is a spec violation), or because strict mode demands enforcement regardless of metadata (FAPI 2.0 / Open Banking / high-assurance regimes).
See: https://www.rfc-editor.org/rfc/rfc9207#section-2.4
var ErrLoginTimeout = errors.New("login timed out")
ErrLoginTimeout is returned by LoginWithBrowser when no callback reaches the loopback listener within BrowserLoginRequest.Timeout. The returned error wraps it with the timeout ("login timed out after 5m0s"), so match it with errors.Is. Cancelling the caller's ctx returns ctx.Err() instead, so a caller can tell "the user walked away" from "we gave up".
var ErrMetadataIssuerMismatch = errors.New("AS metadata issuer does not match discovery URL")
ErrMetadataIssuerMismatch is returned by DiscoverAS when the AS metadata document's `issuer` field does not match the URL the document was fetched from (RFC 8414 §3.3 — the issuer identifier MUST be identical to the URL used to construct the well-known URL). A mismatch means a server is claiming an identity it doesn't control at the discovery URL; the document MUST be rejected per the spec.
See: https://www.rfc-editor.org/rfc/rfc8414#section-3.3
ErrRegistrationUnauthorized is returned by GetRegistration (and, in #169 / #170, UpdateRegistration / DeleteRegistration) when the authorization server rejects the registration access token. Per RFC 7592 the server returns 401 for any auth failure — wrong token, missing token, or unknown client_id — so callers cannot use this error to distinguish those cases.
Functions ¶
func ApproveConsent ¶ added in v0.1.39
ApproveConsent returns an OpenBrowser function that walks an authorization request the way a user clicking Approve would. It is meant for demo and test authorization servers that answer /authorize with a consent page instead of redirecting, where FollowRedirects would stop at the page.
Redirects are followed with a cookie jar, since consent forms usually key on a session; if httpClient has no Jar, a copy of it gets one and the caller's client is left untouched. A server that redirects straight to the redirect_uri completes on the GET. Otherwise the first <form> on the page is posted with its inputs as served plus the one submit button whose value reads as approval ("approve", "allow", "accept", "authorize", "consent", "yes"). A form with no such button is refused rather than guessed at, so a login page asking for a password is never submitted.
Error statuses fail fast the same way FollowRedirects does. Pages are scanned with regular expressions, not an HTML parser: this handles the plain consent forms demo servers serve, not arbitrary login UIs.
func FollowRedirects ¶ added in v0.0.65
FollowRedirects returns an OpenBrowser function that performs the OAuth authorization flow by following HTTP redirects instead of opening a browser. This enables headless environments: CI, conformance testing, CLI tools.
The returned function GETs the authorization URL using the provided HTTP client. The authorization server redirects to the loopback callback URI, which the LoginWithBrowser loopback server catches — exactly as a browser would.
Usage:
cred, err := authClient.LoginWithBrowser(ctx, &client.BrowserLoginRequest{
ClientID: "my-cli",
OpenBrowser: client.FollowRedirects(nil),
})
The httpClient should follow redirects (default http.Client behavior). If nil, a default client is used. For authorization servers that require form-based login (e.g., Keycloak), the httpClient must handle cookie/session management and form POST — FollowRedirects is designed for AS endpoints that auto-approve (test/mock servers) or for pre-authenticated sessions. ApproveConsent covers servers that answer with a consent form.
If the GET ends anywhere other than the authorization URL's redirect_uri with a status of 400 or above, the returned error carries the status and the AS's error/error_description (or a snippet of the body), so LoginWithBrowser fails at once instead of waiting out its timeout for a callback that will never arrive. Any other non-callback ending, such as a 200 login page, still returns nil.
See: https://github.com/panyam/oneauth/issues/71 See: https://github.com/panyam/oneauth/issues/387
func IsLocalhost ¶ added in v0.0.68
IsLocalhost returns true if the URL points to a loopback address (localhost, 127.0.0.1, or ::1). This is used to exempt local development servers from HTTPS enforcement.
func LoopbackRedirectURI ¶ added in v0.1.39
LoopbackRedirectURI is the redirect URI LoginWithBrowser sends for a listener at addr when BrowserLoginRequest.RedirectURI is empty: http://localhost:<port>/callback. Only the port is taken from addr, so a listener on 127.0.0.1 or [::1] yields the same string. A caller that registers a client via DCR before the login registers exactly this string, which is what an AS matching redirect URIs byte-for-byte requires.
func MintClientAssertion ¶ added in v0.0.83
func MintClientAssertion(clientID, audience string, cfg ClientAssertionConfig) (string, error)
MintClientAssertion produces a signed JWT bearing the client's identity, suitable for use as the `client_assertion` form parameter at the token / introspection / revocation endpoints.
When cfg.Audience is non-empty it overrides the positional audience argument — use the field to target RFC 7523bis ASes that require `aud == issuer URL`; rely on the positional default (token endpoint URL) for OIDC Core §9-compliant ASes.
Claims set per RFC 7523 §3 + OIDC Core §9:
iss = clientID sub = clientID aud = cfg.Audience if set, else `audience` argument jti = random 128-bit hex string iat = now exp = now + cfg.Lifetime (or default)
Returns the compact JWS string ready to drop into a form value.
func ValidateCIMDURL ¶ added in v0.0.68
ValidateCIMDURL validates a Client ID Metadata Document URL. Per draft-ietf-oauth-client-id-metadata-document:
- MUST use HTTPS (except localhost for development/testing)
- MUST contain a non-root path (the URL must identify a specific document)
func ValidateHTTPS ¶ added in v0.0.68
func ValidateHTTPS(meta *ASMetadata) error
ValidateHTTPS checks that Authorization Server endpoints use HTTPS. Localhost URLs are exempt for development and testing. Returns nil if metadata is nil (nothing to validate).
func ValidateIss ¶ added in v0.1.13
ValidateIss applies the RFC 9207 §2.4 enforcement rules to an `iss` query parameter received on an authorization-response redirect.
Parameters:
- iss: the value of the `iss` query parameter from the redirect URL (empty string when the AS did not include it).
- expectedIssuer: the issuer identifier the client expects (from ASMetadata.Issuer or the configured trust anchor).
- asAdvertisedSupport: whether the AS metadata document advertised `authorization_response_iss_parameter_supported: true`. Callers reading from `client.ASMetadata.AuthorizationResponseIssParameterSupported` (a `*bool`) should collapse the tristate at the call site: `meta.AuthorizationResponseIssParameterSupported != nil && *meta.…`.
- strict: when true, an empty iss is always rejected regardless of `asAdvertisedSupport`. Use for FAPI 2.0 / Open Banking / regulated industries that mandate iss enforcement even against pre-9207 ASes.
Truth table:
iss | advertised | strict | result --------|------------|--------|----------------- matches | any | any | nil (accept) differs | any | any | ErrIssMismatch empty | true | any | ErrIssMissing empty | false | false | nil (legacy AS — accept) empty | false | true | ErrIssMissing (strict)
Comparison is byte-for-byte. RFC 9207 §2.4 inherits comparison semantics from the JWT `iss` claim defined in RFC 9068 §2.1.1, which is byte-equal — not RFC 3986 §6.2 syntax-based URL normalization. Concretely:
- "https://auth.example.com" and "https://auth.example.com/" are DIFFERENT issuers (trailing slash is significant).
- "https://auth.example.com" and "HTTPS://auth.example.com" are DIFFERENT issuers (scheme case is significant).
The MCP conformance scenario `sep-2468-client-no-normalization` (check id `auth/iss-normalized` in modelcontextprotocol/conformance) grades to exactly this rule: any normalization a client applies will fail the scenario.
Returns nil on accept, or a sentinel error (ErrIssMismatch / ErrIssMissing). Sentinel errors are safe to inspect via errors.Is.
Calling with an empty expectedIssuer is a programmer error and returns a non-sentinel error — there is no defensible accept/reject decision when the trust anchor is unset.
See: https://www.rfc-editor.org/rfc/rfc9207#section-2.4 See: https://www.rfc-editor.org/rfc/rfc9068#section-2.1.1 See: https://github.com/modelcontextprotocol/conformance (auth/iss-normalized)
Types ¶
type ASMetadata ¶ added in v0.0.56
type ASMetadata struct {
// Required
Issuer string `json:"issuer"`
TokenEndpoint string `json:"token_endpoint"`
// Recommended
AuthorizationEndpoint string `json:"authorization_endpoint,omitempty"`
JWKSURI string `json:"jwks_uri,omitempty"`
// Optional
RegistrationEndpoint string `json:"registration_endpoint,omitempty"`
IntrospectionEndpoint string `json:"introspection_endpoint,omitempty"`
RevocationEndpoint string `json:"revocation_endpoint,omitempty"`
UserinfoEndpoint string `json:"userinfo_endpoint,omitempty"`
DeviceAuthorizationEndpoint string `json:"device_authorization_endpoint,omitempty"` // RFC 8628 §4
// Supported features
ScopesSupported []string `json:"scopes_supported,omitempty"`
ResponseTypesSupported []string `json:"response_types_supported,omitempty"`
GrantTypesSupported []string `json:"grant_types_supported,omitempty"`
CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported,omitempty"`
TokenEndpointAuthMethods []string `json:"token_endpoint_auth_methods_supported,omitempty"`
TokenEndpointAuthSigningAlgValuesSupported []string `json:"token_endpoint_auth_signing_alg_values_supported,omitempty"`
// DPoPSigningAlgValuesSupported lists the JWS algorithms the server
// accepts on a DPoP proof (RFC 9449 §5.1). Its presence is how a
// client learns the server supports DPoP at all. Check a key against
// it with DPoPKey.SupportedBy before sending proofs.
DPoPSigningAlgValuesSupported []string `json:"dpop_signing_alg_values_supported,omitempty"`
// AuthorizationResponseIssParameterSupported surfaces the RFC 9207
// advertisement value from the AS metadata document. Pointer semantics
// are load-bearing for the §2.4 enforcement rules a consumer applies
// after `BrowserLoginRequest.OnCallback` runs:
//
// - non-nil pointing to true → AS commits to including `iss` on every
// authorization response; a callback with empty `iss` MUST be rejected.
// - non-nil pointing to false → AS explicitly disclaims support;
// consumers may skip iss enforcement (legacy-AS posture).
// - nil (field absent) → AS predates RFC 9207 / didn't advertise;
// consumers may treat as legacy-AS unless a stricter policy (e.g.
// FAPI 2.0) demands enforcement regardless.
//
// The distinction between explicit false and absent is preserved so a
// strict-mode validator can tell "AS told us no" from "AS didn't say."
//
// RFC 9207 §3:
// https://www.rfc-editor.org/rfc/rfc9207#section-3
AuthorizationResponseIssParameterSupported *bool `json:"authorization_response_iss_parameter_supported,omitempty"`
// ClientIdMetadataDocumentSupported surfaces the SEP-991 / Client ID
// Metadata Document advertisement
// (draft-ietf-oauth-client-id-metadata-document): when true the AS
// accepts a URL as the client_id and resolves the metadata document
// itself, so clients SHOULD prefer that over Dynamic Client
// Registration. Absent and explicit false are equivalent here (no
// enforcement rule keys off the distinction), so a plain bool suffices.
ClientIdMetadataDocumentSupported bool `json:"client_id_metadata_document_supported,omitempty"`
}
ASMetadata holds OAuth 2.0 Authorization Server metadata discovered from a well-known endpoint. Fields follow RFC 8414 and OpenID Connect Discovery 1.0.
See: https://www.rfc-editor.org/rfc/rfc8414#section-2
func DiscoverAS ¶ added in v0.0.56
func DiscoverAS(issuerURL string, opts ...DiscoveryOption) (*ASMetadata, error)
DiscoverAS fetches OAuth Authorization Server metadata from well-known endpoints. Equivalent to DiscoverASWithContext(context.Background(), …). Prefer the context-bearing form from any HTTP-serving path so SEP-414 trace propagation reaches the AS's well-known endpoint.
It tries the following URLs in order (per RFC 8414 + OIDC Discovery):
For issuer "https://auth.example.com" (no path):
- https://auth.example.com/.well-known/oauth-authorization-server
- https://auth.example.com/.well-known/openid-configuration
For issuer "https://auth.example.com/tenant1" (with path):
- https://auth.example.com/.well-known/oauth-authorization-server/tenant1
- https://auth.example.com/tenant1/.well-known/openid-configuration
Returns the first successful response. Returns an error if all attempts fail.
The returned metadata's `issuer` field is validated against the fetch URL per RFC 8414 §3.3 — a document claiming an issuer different from the URL it was served at is rejected with ErrMetadataIssuerMismatch (no fallback to the next discovery URL on this failure, since a malicious well-known response shouldn't get a retry).
func DiscoverASWithContext ¶ added in v0.1.14
func DiscoverASWithContext(ctx context.Context, issuerURL string, opts ...DiscoveryOption) (*ASMetadata, error)
DiscoverASWithContext is the context-aware variant of DiscoverAS. The supplied ctx is forwarded onto every well-known HTTP fetch, and a W3C `traceparent` header is injected so an OTel-aware AS can stitch its metadata-serve span into the caller's trace.
type ASMetadataStore ¶ added in v0.0.69
type ASMetadataStore interface {
// Get returns the cached metadata for an issuer if present and not expired.
// Returns (nil, false) on cache miss or expiry.
Get(issuer string) (*ASMetadata, bool)
// Put stores metadata for an issuer with the given TTL. A TTL of 0 means
// use the store's default. Put replaces any existing entry for the issuer.
Put(issuer string, md *ASMetadata, ttl time.Duration)
}
ASMetadataStore caches OAuth Authorization Server metadata by issuer URL. Implementations must be safe for concurrent use by multiple goroutines.
A store is typically shared across multiple [OAuthTokenSource] instances in the same process, so that N resource servers sharing one AS only trigger one discovery fetch instead of N.
The store is a hot-path optimization: discovery is cheap (one HTTP fetch) but redundant across concurrent token sources. Callers opt in by passing a store to WithASMetadataStore.
type AuthClient ¶
type AuthClient struct {
// OnToken is an optional callback invoked after a successful token
// refresh (the refresh_token grant path through refreshTokenLocked).
// It fires AFTER the new credential has been stored via CredentialStore,
// so consumers can use the callback for side-effects (logging, metrics,
// external persistence) that should observe the post-refresh state.
//
// Thread safety: the callback is invoked synchronously from whichever
// goroutine triggered the refresh. Implementations must be thread-safe
// if the AuthClient is shared across goroutines.
//
// Lock contract: the callback runs while the AuthClient internal mutex
// is held — same as CredentialStore.SetCredential. Callbacks must NOT
// re-enter AuthClient methods (GetToken, GetCredential, Login,
// refreshTokenLocked) or they will deadlock. Callbacks should be
// lightweight and non-blocking.
//
// Does NOT fire for initial logins (Login, LoginWithBrowser) — those
// return the credential directly to the caller, who can persist it
// explicitly. Only the automatic refresh_token grant path fires this.
OnToken func(*ServerCredential)
// contains filtered or unexported fields
}
AuthClient is an HTTP client with automatic token management
func NewAuthClient ¶
func NewAuthClient(serverURL string, store CredentialStore, opts ...ClientOption) *AuthClient
NewAuthClient creates a new authenticated HTTP client for a server. If store is nil, a no-op store is used — methods that return credentials (Login, LoginWithBrowser, ClientCredentialsToken) still work and return the credential to the caller, but tokens are not persisted between calls.
func (*AuthClient) ClientCredentials ¶ added in v0.1.2
func (c *AuthClient) ClientCredentials(ctx context.Context, req *ClientCredentialsRequest) (*ServerCredential, error)
ClientCredentials is the consolidated client_credentials grant entry point (RFC 6749 §4.4). It selects the client-authentication method (`client_secret_basic` / `client_secret_post` when ClientSecret is set; `private_key_jwt` when ClientAssertion is non-nil), assembles the form-encoded request — including RFC 8707 `resource` indicators and RFC 9396 `authorization_details` when present — and persists the resulting credential.
func (*AuthClient) ClientCredentialsToken
deprecated
added in
v0.0.56
func (c *AuthClient) ClientCredentialsToken(clientID, clientSecret string, scopes []string) (*ServerCredential, error)
ClientCredentialsToken authenticates using the client_credentials grant (RFC 6749 §4.4) with `client_secret_basic` / `client_secret_post`. Thin wrapper over ClientCredentials — use the request struct directly for RFC 8707 `resource` or RFC 9396 `authorization_details`.
See: https://www.rfc-editor.org/rfc/rfc6749#section-4.4 See: https://github.com/panyam/oneauth/issues/72
Deprecated: use ClientCredentials directly with a context. This wrapper is retained for compatibility and passes context.Background() under the hood.
func (*AuthClient) ClientCredentialsTokenWithAssertion
deprecated
added in
v0.0.83
func (c *AuthClient) ClientCredentialsTokenWithAssertion(clientID string, cfg ClientAssertionConfig, scopes []string) (*ServerCredential, error)
ClientCredentialsTokenWithAssertion is the private_key_jwt wrapper over ClientCredentials (RFC 6749 §4.4 with RFC 7521 §4.2 / RFC 7523 §2.2). Use ClientCredentials directly for resource / authorization detail parameters.
See: https://www.rfc-editor.org/rfc/rfc7523#section-2.2
Deprecated: use ClientCredentials directly with a context. This wrapper is retained for compatibility and passes context.Background() under the hood.
func (*AuthClient) DeviceAuthorization ¶ added in v0.1.25
func (c *AuthClient) DeviceAuthorization(ctx context.Context, req *DeviceAuthorizationRequest) (*DeviceAuthorizationResponse, error)
DeviceAuthorization initiates an RFC 8628 device authorization flow. Resolves `device_authorization_endpoint` from cached AS metadata; if the metadata wasn't pre-loaded or the field is empty, returns an error (no fallback URL — RFC 8628 doesn't define a well-known path).
func (*AuthClient) GetCredential ¶
func (c *AuthClient) GetCredential() (*ServerCredential, error)
GetCredential returns the stored credential for this server
func (*AuthClient) GetToken ¶
func (c *AuthClient) GetToken() (string, error)
GetToken returns the current access token, refreshing if needed
func (*AuthClient) HTTPClient ¶
func (c *AuthClient) HTTPClient() *http.Client
HTTPClient returns the underlying HTTP client with auth handling
func (*AuthClient) IsLoggedIn ¶
func (c *AuthClient) IsLoggedIn() bool
IsLoggedIn returns true if there is a valid (non-expired) credential
func (*AuthClient) JwtBearerGrant ¶ added in v0.1.5
func (c *AuthClient) JwtBearerGrant(ctx context.Context, req *JwtBearerGrantRequest) (*ServerCredential, error)
JwtBearerGrant performs an RFC 7523 §2.1 jwt-bearer grant exchange. Returns a usable ServerCredential containing the access token.
func (*AuthClient) Login ¶
func (c *AuthClient) Login(ctx context.Context, req *LoginRequest) (*ServerCredential, error)
Login authenticates with username/password (RFC 6749 §4.3 resource-owner password credentials grant) and stores the resulting credential.
func (*AuthClient) LoginWithBrowser ¶ added in v0.0.56
func (c *AuthClient) LoginWithBrowser(ctx context.Context, req *BrowserLoginRequest) (*ServerCredential, error)
LoginWithBrowser performs an OAuth 2.0 authorization code flow with PKCE for CLI/headless clients (RFC 8252). It:
- Generates PKCE verifier + challenge
- Generates a random state parameter for CSRF protection
- Starts a temporary loopback HTTP server to catch the redirect
- Opens the user's browser to the authorization URL
- Waits for the callback with the authorization code
- Validates the state parameter
- Exchanges the code for tokens using the code_verifier
- Stores the credential via the AuthClient's CredentialStore
See: https://www.rfc-editor.org/rfc/rfc8252 (OAuth 2.0 for Native Apps) See: https://www.rfc-editor.org/rfc/rfc7636 (PKCE)
func (*AuthClient) Logout ¶
func (c *AuthClient) Logout() error
Logout removes the credential for this server
func (*AuthClient) PollDeviceToken ¶ added in v0.1.25
func (c *AuthClient) PollDeviceToken(ctx context.Context, req *PollDeviceTokenRequest) (*ServerCredential, error)
PollDeviceToken executes one poll attempt against the AS's token endpoint. Maps the four RFC 8628 §3.5 polling errors to typed sentinels (ErrAuthorizationPending, ErrSlowDown, ErrAccessDenied, ErrExpiredToken) so callers can branch via errors.Is. Any other error response from the token endpoint (invalid_grant, invalid_client, ...) is a *TokenError; network errors bubble up as they are.
On success returns the issued credential. The caller is responsible for the polling loop, interval enforcement, and timeout.
func (*AuthClient) RefreshToken ¶ added in v0.1.16
func (c *AuthClient) RefreshToken(ctx context.Context, req *RefreshTokenRequest) (*ServerCredential, error)
RefreshToken exchanges a caller-supplied refresh_token for a new access token via RFC 6749 §6 against the AS-discovered token endpoint. Form-encoded — distinct from the internal refreshTokenLocked path that targets the legacy oneauth-native /auth/cli/token JSON endpoint and is reserved for credentials stored via Login. RefreshToken is the standards-compliant entry point used by external CLI tools (e.g., `oneauth token refresh`) against any RFC 8414 / OIDC AS.
Storage behavior mirrors ClientCredentials: the new credential is persisted via the configured CredentialStore. The supplied refresh token is carried forward when the AS does not rotate per §6.
func (*AuthClient) ServerURL ¶
func (c *AuthClient) ServerURL() string
ServerURL returns the server URL this client is configured for
func (*AuthClient) TokenExchange ¶ added in v0.1.5
func (c *AuthClient) TokenExchange(ctx context.Context, req *TokenExchangeRequest) (*TokenExchangeResponse, error)
TokenExchange performs an RFC 8693 token exchange. The caller can authenticate via shared secret (ClientID + ClientSecret) or via private_key_jwt (ClientID + ClientAssertion). Returns the parsed response including `issued_token_type`.
type AuthTransport ¶
type AuthTransport struct {
Base http.RoundTripper
Token string
}
AuthTransport wraps an http.RoundTripper to add Authorization headers
func NewAuthTransport ¶
func NewAuthTransport(token string) *AuthTransport
NewAuthTransport creates an AuthTransport with the given token
func NewAuthTransportWithBase ¶
func NewAuthTransportWithBase(base http.RoundTripper, token string) *AuthTransport
NewAuthTransportWithBase creates an AuthTransport with a custom base transport
type AuthorizeError ¶ added in v0.1.40
AuthorizeError is an error response from the authorization endpoint itself, before any redirect back to the client: the AS refused to start the flow, e.g. an unknown client_id or an unregistered redirect_uri (RFC 6749 §4.1.2.1 says the AS must not redirect in those cases). FollowRedirects and ApproveConsent return it when /authorize ends with a status of 400 or above anywhere other than the redirect_uri, and LoginWithBrowser passes it on wrapped in a *BrowserOpenError, so use errors.As to find it.
Code and Description are the OAuth error and error_description when the body is a JSON error object. The headless openers list application/json in Accept, so an AS that negotiates its error responses (oneauth does) sends one. Many servers answer with an HTML page regardless (Keycloak does); then Code is empty and Body holds the page's visible text, up to 300 characters.
See: https://www.rfc-editor.org/rfc/rfc6749#section-4.1.2.1
func (*AuthorizeError) Error ¶ added in v0.1.40
func (e *AuthorizeError) Error() string
Error renders the status plus the OAuth error (or body snippet), e.g. "authorization endpoint answered 400 Bad Request: invalid_client: unknown client".
type BrowserLoginRequest ¶ added in v0.1.8
type BrowserLoginRequest struct {
// AuthorizationEndpoint is the AS authorization URL.
// If empty, auto-discovered via DiscoverAS(serverURL) using the
// AuthClient's server URL.
AuthorizationEndpoint string
// TokenEndpoint is the AS token URL for code exchange.
// If empty, uses the AuthClient's configured tokenEndpoint.
TokenEndpoint string
// ClientID identifies this client to the authorization server.
// Required.
ClientID string
// Scopes to request (e.g., []string{"openid", "read", "write"}).
Scopes []string
// CallbackPort is the port for the loopback redirect server.
// If 0, a random available port is chosen. Ignored when Listener or
// RedirectURI is set.
CallbackPort int
// Listener, when set, is the loopback listener the callback is served
// on, in place of one LoginWithBrowser binds itself. A caller doing DCR
// binds it first, registers LoopbackRedirectURI(l.Addr()) (or its own
// RedirectURI), and hands it over, so the port registered is the port
// served with no release-and-rebind window in between.
//
// LoginWithBrowser takes ownership: it closes the listener before
// returning, on every path including early validation errors. A
// listener serves one login.
Listener net.Listener
// RedirectURI, when set, is sent byte-for-byte as redirect_uri on the
// authorization request and the code exchange, and the callback is
// served on its path. Use it when the registered redirect differs from
// LoopbackRedirectURI's default, e.g. an IP literal (which RFC 8252 §8.3
// prefers over "localhost") or a path other than /callback.
//
// It must be an http URI on a loopback host (localhost, 127.0.0.1 or
// [::1]) with an explicit port. With Listener set the port must equal
// the listener's; without one, LoginWithBrowser binds the URI's host
// and port. Anything else fails before the browser opens.
//
// See: https://www.rfc-editor.org/rfc/rfc8252#section-7.3
RedirectURI string
// Timeout for the entire flow (waiting for user to complete browser login).
// Defaults to 5 minutes.
Timeout time.Duration
// OpenBrowser is called to open the authorization URL in the user's browser.
// If nil, uses the platform default (open/xdg-open/start).
// Set to a custom function for testing or headless environments.
OpenBrowser func(url string) error
// HTTPClient is used for the token exchange request.
// If nil, uses http.DefaultClient.
HTTPClient *http.Client
// Resource is the RFC 8707 resource indicator — the canonical URI of the
// target resource server (e.g., "https://api.example.com"). When set, it's
// included in both the authorization request and token exchange to bind the
// token to a specific audience. MCP spec requires this parameter.
//
// See: https://www.rfc-editor.org/rfc/rfc8707
Resource string
// ClientSecret is the client's secret for confidential clients.
// If empty, the client is treated as a public client (auth method "none")
// and only client_id is sent in the token exchange.
// When set, the auth method is negotiated based on AS metadata
// (token_endpoint_auth_methods_supported) per RFC 6749 §2.3.
//
// See: https://github.com/panyam/oneauth/issues/72
ClientSecret string
// TokenEndpointAuthMethods specifies the auth methods supported by the AS
// token endpoint (e.g., ["client_secret_post", "client_secret_basic"]).
// This is used when AuthorizationEndpoint and TokenEndpoint are explicitly
// provided and discovery is skipped — without this field, the client
// defaults to client_secret_basic which may not be what the AS supports.
//
// Callers that perform their own AS discovery (e.g., via PRM → AS metadata)
// should pass token_endpoint_auth_methods_supported here so the client
// negotiates the correct auth method per RFC 6749 §2.3.
//
// If empty and discovery is skipped, defaults to client_secret_basic
// per RFC 6749 §2.3.1.
//
// See: https://www.rfc-editor.org/rfc/rfc6749#section-2.3
// See: https://github.com/panyam/oneauth/issues/74
TokenEndpointAuthMethods []string
// ClientAssertion, when non-nil, switches the code-exchange step to
// private_key_jwt client authentication (RFC 7521 §4.2 / RFC 7523
// §2.2 / OIDC Core §9). The client signs a fresh JWT with the
// configured private key and presents it as `client_assertion`
// instead of using ClientSecret. Mutually exclusive with
// ClientSecret — when both are set, ClientAssertion wins.
ClientAssertion *ClientAssertionConfig
// OnCallback, when non-nil, fires after the loopback receives the
// authorization-code redirect and after state validation, but BEFORE
// the token exchange. Consumers use it to apply per-policy validation
// of the RFC 9207 `iss` query parameter (which oneauth surfaces but
// deliberately does not enforce — the spec leaves enforcement to the
// client per §2.4). Returning a non-nil error aborts the flow and the
// error is returned (wrapped) from LoginWithBrowser; no token exchange
// is performed.
OnCallback func(ctx context.Context, params CallbackParams) error
}
BrowserLoginRequest configures the authorization code + PKCE flow for CLI and headless clients (RFC 8252 + RFC 7636).
type BrowserOpenError ¶ added in v0.1.40
BrowserOpenError is returned by LoginWithBrowser when the BrowserLoginRequest.OpenBrowser function fails: no browser to launch, or a headless opener that could not complete the authorization request (a consent form with no approve button). Err is the opener's error, and Unwrap exposes it, so errors.As still finds an *AuthorizeError returned by FollowRedirects or ApproveConsent.
func (*BrowserOpenError) Error ¶ added in v0.1.40
func (e *BrowserOpenError) Error() string
Error renders "failed to open browser (URL: <url>): <err>".
func (*BrowserOpenError) Unwrap ¶ added in v0.1.40
func (e *BrowserOpenError) Unwrap() error
Unwrap returns the opener's error.
type CallbackError ¶ added in v0.1.40
CallbackError is an error delivered to the loopback redirect_uri: the AS started the flow but ended it with ?error=... instead of a code. Code is the RFC 6749 §4.1.2.1 error code; "access_denied" means the user declined, which a caller should not answer by opening the consent page again.
See: https://www.rfc-editor.org/rfc/rfc6749#section-4.1.2.1
func (*CallbackError) Error ¶ added in v0.1.40
func (e *CallbackError) Error() string
Error renders "authorization error: <code>: <description>".
type CallbackParams ¶ added in v0.1.11
type CallbackParams struct {
// Iss is the RFC 9207 `iss` query parameter — the issuer identifier of
// the authorization response. Empty when the AS does not send it (legacy
// ASes pre-dating RFC 9207). Consumers MAY treat absence as a hard
// failure for high-assurance flows; oneauth itself does not enforce.
Iss string
// Code is the OAuth 2.0 `code` query parameter (the authorization code
// to be exchanged at the token endpoint). Non-empty by the time the
// hook fires.
Code string
// State is the OAuth 2.0 `state` query parameter as echoed back by the
// AS. Already validated against the client-issued state before the hook
// fires — exposed for logging / audit only.
State string
}
CallbackParams carries the loopback-redirect query parameters that the authorization server sent on the success path. Passed to BrowserLoginRequest.OnCallback so the caller can apply policy (e.g., RFC 9207 iss-issuer matching) before the code is exchanged.
type ClientAssertionConfig ¶ added in v0.0.83
type ClientAssertionConfig struct {
// PrivateKey is the asymmetric signing key — *rsa.PrivateKey or
// *ecdsa.PrivateKey. Must match the public key registered with the
// AS via DCR (RFC 7591) for the same `client_id`.
PrivateKey crypto.PrivateKey
// SigningAlg is the JWS alg value: "RS256" or "ES256". Must match
// the alg the AS registered for this client.
SigningAlg string
// KeyID, when non-empty, is set as the `kid` JWS header so the AS
// can disambiguate among multiple registered keys for the same
// client. Optional — clients with a single key may leave it blank.
KeyID string
// Lifetime is how long the minted assertion is valid for. Defaults
// to 60s (recommended ceiling for FAPI / most IdPs). Must be ≤
// 5min to satisfy OneAuth's server-side assertion lifetime cap;
// other AS implementations are typically stricter.
Lifetime time.Duration
// Audience, when non-empty, overrides the `aud` claim of the minted
// assertion. Empty falls back to the positional `audience` argument
// of MintClientAssertion (typically the token endpoint URL).
//
// RFC 7523bis (current OAuth WG draft) requires `aud` to be the AS
// issuer identifier; OIDC Core §9 historically permitted the token
// endpoint URL. ASes in the wild differ — set this when targeting a
// 7523bis-strict AS that rejects the token endpoint URL.
Audience string
}
ClientAssertionConfig carries the material a client needs to mint a `private_key_jwt` assertion. Construct once per AuthClient and reuse; each MintClientAssertion call generates a fresh `jti`, `iat`, and `exp` so assertions are single-use.
type ClientCredentialsRequest ¶ added in v0.1.2
type ClientCredentialsRequest struct {
// ClientID identifies the client to the AS. Required.
ClientID string
// ClientSecret authenticates the client when ClientAssertion is nil.
// Sent via the negotiated client_secret_basic / client_secret_post
// method (RFC 6749 §2.3.1).
ClientSecret string
// ClientAssertion, when non-nil, switches client authentication to
// the private_key_jwt path (RFC 7521 §4.2 / RFC 7523 §2.2 / OIDC
// Core §9). ClientSecret is ignored when this is set.
ClientAssertion *ClientAssertionConfig
// Scopes requested for the access token. Sent as the space-delimited
// `scope` form value.
Scopes []string
// Resources are RFC 8707 resource indicators — absolute URIs naming
// the resource server(s) the token will be used at. Emitted as
// repeated `resource` form values per §2.
Resources []string
// AuthorizationDetails carries RFC 9396 rich authorization
// requirements. JSON-encoded into a single `authorization_details`
// form value per §6.1.
AuthorizationDetails []core.AuthorizationDetail
}
ClientCredentialsRequest is the gRPC-shape input to ClientCredentials: every parameter the client_credentials grant accepts, in one place. Use the request struct directly for new code; ClientCredentialsToken and ClientCredentialsTokenWithAssertion remain as 3-line wrappers for existing callers.
type ClientCredentialsSource ¶ added in v0.0.68
type ClientCredentialsSource struct {
// TokenEndpoint is the authorization server's token URL.
TokenEndpoint string
// ClientID identifies this client to the authorization server.
ClientID string
// ClientSecret authenticates this client when ClientAssertion is nil.
// Sent via the negotiated client_secret_basic or client_secret_post
// method (RFC 6749 §2.3.1).
ClientSecret string
// ClientAssertion, when non-nil, switches the source to the
// private_key_jwt client-authentication path (RFC 7521 §4.2 /
// RFC 7523 §2.2 / OIDC Core §9). ClientSecret is ignored. The same
// caching, OnToken, and ProactiveRefresher machinery applies.
ClientAssertion *ClientAssertionConfig
// Scopes to request.
Scopes []string
// Resources are RFC 8707 resource indicators forwarded on every
// token request. Emitted as repeated `resource` form values per §2.
Resources []string
// AuthorizationDetails are RFC 9396 rich authorization requirements
// forwarded on every token request as the `authorization_details`
// JSON-encoded form value per §6.1.
AuthorizationDetails []core.AuthorizationDetail
// Refresher enables proactive token refresh before expiry. When nil or
// Refresher.Buffer == 0, refresh is reactive — happens on the next
// Token() call after expiry.
//
// Typical values: Buffer of 30s-60s for tokens with 5-15 minute lifetimes.
// The goroutine starts lazily on the first Token() call and runs until
// Close() is called on the source.
Refresher *ProactiveRefresher
// OnToken is an optional callback invoked after every successful token
// acquisition — both the initial fetch and all subsequent refreshes
// (reactive and proactive). Use this to persist the credential to an
// external store (file, database) without implementing a full
// CredentialStore.
//
// Thread safety: the callback may be invoked from the caller's
// goroutine (initial/reactive path) or from the background refresh
// goroutine (proactive path). Implementations must be thread-safe.
//
// The callback holds no locks of the source, so calling back into
// Token() from within the callback is safe (but useless — it will
// return the same token that was just passed in).
//
// The credential passed to the callback is the same value returned
// from AuthClient.ClientCredentialsToken. Mutating it from within the
// callback is not safe.
OnToken func(*ServerCredential)
// contains filtered or unexported fields
}
ClientCredentialsSource implements TokenSource for machine-to-machine auth using the OAuth 2.0 client_credentials grant (RFC 6749 §4.4).
It caches the access token and automatically refreshes it when expired. TokenForScopes supports scope step-up by merging additional scopes and invalidating the cache.
See: https://www.rfc-editor.org/rfc/rfc6749#section-4.4
func (*ClientCredentialsSource) Close ¶ added in v0.0.69
func (s *ClientCredentialsSource) Close() error
Close stops the background refresh goroutine if one is running. Safe to call multiple times. Returns nil always (io.Closer compliance). After Close, subsequent Token() calls still work reactively.
func (*ClientCredentialsSource) Token ¶ added in v0.0.68
func (s *ClientCredentialsSource) Token() (string, error)
Token returns a cached access token if still valid, or fetches a new one via the client_credentials grant.
If Refresher.Buffer > 0, the background refresh goroutine starts lazily on the first Token() call.
Invokes OnToken (if set) after a successful fetch, outside the source's mutex — callbacks are free to call back into Token() without deadlock.
func (*ClientCredentialsSource) TokenForScopes ¶ added in v0.0.68
func (s *ClientCredentialsSource) TokenForScopes(scopes []string) (string, error)
TokenForScopes invalidates the cached token, merges the requested scopes with the existing scope set (using core.UnionScopes), and fetches a fresh token with the combined scopes.
type ClientOption ¶
type ClientOption func(*AuthClient)
ClientOption configures an AuthClient
func WithASMetadata ¶ added in v0.0.65
func WithASMetadata(meta *ASMetadata) ClientOption
WithASMetadata pre-populates AS discovery metadata, enabling auth method negotiation in ClientCredentialsToken without a separate discovery request. Useful when DiscoverAS has already been called or for testing.
func WithClientSecret ¶ added in v0.1.43
func WithClientSecret(secret string) ClientOption
WithClientSecret gives the automatic refresh a confidential client's secret, so it can authenticate the refresh_token grant as RFC 6749 §6 requires. The secret is held in memory only and never written to the CredentialStore; a process that loads a credential from disk passes it again here.
It authenticates as the client the credential was issued to (ServerCredential.ClientID), so one AuthClient serves one confidential client. With AS metadata the method follows its advertised token_endpoint_auth_methods_supported, as RefreshToken does; on the legacy JSON path the secret goes in the body. Explicit calls such as RefreshToken take their own ClientSecret and ignore this one.
func WithDPoPKey ¶ added in v0.1.39
func WithDPoPKey(key *DPoPKey) ClientOption
WithDPoPKey binds this client's tokens to a proof-of-possession key (RFC 9449). Token requests carry a DPoP proof, issued tokens come back bound to the key, and requests through the client's HTTP transport present them under the DPoP scheme with a fresh proof each time.
Leaving it unset keeps every request on the bearer path, which is what a client that has not adopted DPoP wants and what every existing caller gets.
The key must outlive the tokens issued against it. A client that discards it holds tokens nothing can present.
func WithHTTPClient ¶
func WithHTTPClient(client *http.Client) ClientOption
WithHTTPClient sets a custom base HTTP client (for timeouts, TLS config, etc.) The transport from this client will be wrapped with auth handling.
func WithTokenEndpoint ¶
func WithTokenEndpoint(path string) ClientOption
WithTokenEndpoint sets a custom token endpoint path
func WithTransport ¶
func WithTransport(transport http.RoundTripper) ClientOption
WithTransport sets a custom base transport (for connection pooling, proxies, etc.)
type ClientRegistrationRequest ¶ added in v0.0.68
type ClientRegistrationRequest struct {
ClientID string `json:"client_id,omitempty"`
ClientName string `json:"client_name"`
ClientURI string `json:"client_uri,omitempty"`
RedirectURIs []string `json:"redirect_uris"`
GrantTypes []string `json:"grant_types,omitempty"`
ResponseTypes []string `json:"response_types,omitempty"`
TokenEndpointAuthMethod string `json:"token_endpoint_auth_method,omitempty"`
Scope string `json:"scope,omitempty"`
// ApplicationType identifies the client class per OpenID Connect Dynamic
// Client Registration 1.0 §2: "native" for installed/CLI/SDK clients,
// "web" for web-server-hosted clients. omitempty so existing callers stay
// wire-compatible; consumers whose spec requires it (e.g. MCP per SEP-837)
// set it explicitly.
ApplicationType string `json:"application_type,omitempty"`
// DPoPBoundAccessTokens asks the AS to always issue this client
// DPoP-bound access tokens and to refuse its token requests that
// carry no proof (RFC 9449 §5.2).
DPoPBoundAccessTokens bool `json:"dpop_bound_access_tokens,omitempty"`
// RequirePushedAuthorizationRequests asks the AS to accept this
// client's authorization requests only through PAR (RFC 9126 §6).
RequirePushedAuthorizationRequests bool `json:"require_pushed_authorization_requests,omitempty"`
}
ClientRegistrationRequest is the payload for RFC 7591 Dynamic Client Registration and the RFC 7592 §2.2 update body. This represents a client requesting registration (or replacing its registration) at an authorization server's endpoint.
On register the ClientID field is unused (the server assigns the value). On update the ClientID MUST equal the client's existing identifier — the server returns 400 otherwise. UpdateRegistration auto-fills it for callers.
See: https://www.rfc-editor.org/rfc/rfc7591#section-2 See: https://www.rfc-editor.org/rfc/rfc7592#section-2.2
type ClientRegistrationResponse ¶ added in v0.0.68
type ClientRegistrationResponse struct {
ClientID string `json:"client_id"`
ClientSecret string `json:"client_secret,omitempty"`
ClientIDIssuedAt int64 `json:"client_id_issued_at,omitempty"`
ClientSecretExpiresAt int64 `json:"client_secret_expires_at,omitempty"`
ClientName string `json:"client_name,omitempty"`
ClientURI string `json:"client_uri,omitempty"`
RedirectURIs []string `json:"redirect_uris,omitempty"`
GrantTypes []string `json:"grant_types,omitempty"`
TokenEndpointAuthMethod string `json:"token_endpoint_auth_method,omitempty"`
Scope string `json:"scope,omitempty"`
// DPoPBoundAccessTokens is the AS's record of the RFC 9449 §5.2
// setting. An AS that ignores the field leaves it false.
DPoPBoundAccessTokens bool `json:"dpop_bound_access_tokens,omitempty"`
// RequirePushedAuthorizationRequests is the AS's record of RFC 9126 §6
// require_pushed_authorization_requests for this client.
RequirePushedAuthorizationRequests bool `json:"require_pushed_authorization_requests,omitempty"`
// RFC 7592 §3 — management credentials. RegistrationClientURI is the
// management endpoint for this specific registration; RegistrationAccessToken
// is the Bearer token that authorizes calls against it.
RegistrationAccessToken string `json:"registration_access_token,omitempty"`
RegistrationClientURI string `json:"registration_client_uri,omitempty"`
}
ClientRegistrationResponse is the parsed response from a DCR endpoint, extended with the RFC 7592 §3 management credentials so callers can subsequently use GetRegistration / UpdateRegistration / DeleteRegistration against the issuer's management endpoint.
See: https://www.rfc-editor.org/rfc/rfc7591#section-3.2.1 See: https://www.rfc-editor.org/rfc/rfc7592#section-3
func RegisterClient ¶ added in v0.0.68
func RegisterClient(endpoint string, meta ClientRegistrationRequest, httpClient *http.Client) (*ClientRegistrationResponse, error)
RegisterClient performs RFC 7591 Dynamic Client Registration against the given endpoint. Returns the assigned client_id and optional client_secret.
If httpClient is nil, http.DefaultClient is used.
type CredentialStore ¶
type CredentialStore interface {
// GetCredential retrieves a credential for a server URL
// Returns nil, nil if no credential exists for the server
GetCredential(serverURL string) (*ServerCredential, error)
// SetCredential stores a credential for a server URL
SetCredential(serverURL string, cred *ServerCredential) error
// RemoveCredential removes a credential for a server URL
RemoveCredential(serverURL string) error
// ListServers returns all server URLs with stored credentials
ListServers() ([]string, error)
// Save persists any pending changes (for stores that batch writes)
Save() error
}
CredentialStore defines the interface for storing and retrieving credentials
type DPoPKey ¶ added in v0.1.39
type DPoPKey struct {
// contains filtered or unexported fields
}
DPoPKey is a client's proof-of-possession key pair (RFC 9449). Tokens the authorization server issues against it are bound to it, so a token that leaks is unusable by whoever holds it without this key.
One key covers a client's whole session with a server: the same key signs the proof on every token request, every refresh, and every resource request. Hold it for as long as you hold the tokens it is bound to, and no longer — the binding dies with the key, which is the point.
The exception is RFC 9449 §10 `dpop_jkt`, where the spec notes the authorization-code binding "only provides similar protections when a unique DPoP key is used for each authorization request". A client that wants that protection generates a key per authorization rather than per session.
A DPoPKey is safe for concurrent use. It holds no per-request state; every proof carries a fresh `jti`.
See: https://www.rfc-editor.org/rfc/rfc9449
func NewDPoPKey ¶ added in v0.1.39
NewDPoPKey generates a fresh ES256 (P-256) key.
ES256 rather than RSA because a client mints a proof on every single request: P-256 signing is roughly two orders of magnitude cheaper than RSA-2048, and the key is ephemeral so RSA's compatibility advantage buys nothing. Use NewDPoPKeyFromECDSA when the key must outlive the process and is loaded from storage.
func NewDPoPKeyFromECDSA ¶ added in v0.1.39
func NewDPoPKeyFromECDSA(key *ecdsa.PrivateKey) (*DPoPKey, error)
NewDPoPKeyFromECDSA wraps an existing P-256 private key, for a client that persists its key across restarts (an installed application holding tokens in a keychain, say). Curves other than P-256 are rejected: the `alg` this mints is ES256, and signing an ES256 header with a P-384 key produces a proof every server rejects.
func NewDPoPKeyFromEd25519 ¶ added in v0.1.43
func NewDPoPKeyFromEd25519(key ed25519.PrivateKey) (*DPoPKey, error)
NewDPoPKeyFromEd25519 wraps an existing Ed25519 private key, for a client that persists its key across restarts.
func NewEd25519DPoPKey ¶ added in v0.1.43
NewEd25519DPoPKey generates a fresh Ed25519 key, which signs proofs as EdDSA (RFC 8037). FAPI 2.0 §5.4.1 allows EdDSA alongside ES256 and PS256, and Ed25519 signing is cheaper again than P-256. Check the server accepts it first with SupportedBy: a server that only advertises ES256 refuses every proof this key signs.
func (*DPoPKey) Algorithm ¶ added in v0.1.39
Algorithm returns the JWS algorithm this key signs proofs with.
func (*DPoPKey) Proof ¶ added in v0.1.39
Proof mints a DPoP proof JWT for one request (RFC 9449 §4.2).
accessToken binds the proof to the token being presented, via the `ath` claim. Pass it for a request to a protected resource, which RFC 9449 §7.1 requires; leave it empty at the token endpoint, where no access token exists yet.
Every call produces a fresh `jti`, so a proof is good for exactly one request. Reusing one is a replay and the server rejects it, which matters on any retry path: a request retried after a token refresh needs a new proof, not the one that was already sent.
func (*DPoPKey) ProofWithNonce ¶ added in v0.2.1
ProofWithNonce is Proof plus a server-issued `nonce` claim (RFC 9449 §8 / §9). An empty nonce is the same as Proof.
AuthClient's own transport answers nonce challenges for itself. This is for a caller that presents tokens through a transport of its own, such as an MCP client: on a 401 whose WWW-Authenticate carries use_dpop_nonce, it takes the value from the DPoP-Nonce response header and mints the retry's proof with it. Nonces belong to the server that issued them, so keep them per origin and never send one server's nonce to another.
func (*DPoPKey) SupportedBy ¶ added in v0.1.39
SupportedBy reports whether a server that advertises algs accepts this key's algorithm. An empty list means the server published none, which is not a refusal, so it passes.
Call it after discovery, using ASMetadata.DPoPSigningAlgValuesSupported. Checking up front turns "every request fails with invalid_dpop_proof" into one legible error at configuration time.
type DeleteRegistrationRequest ¶ added in v0.0.81
type DeleteRegistrationRequest struct {
RegistrationClientURI string
RegistrationAccessToken string
// HTTPClient — see GetRegistrationRequest for rationale.
HTTPClient *http.Client
}
DeleteRegistrationRequest is the input to DeleteRegistration.
type DeleteRegistrationResponse ¶ added in v0.0.81
type DeleteRegistrationResponse struct{}
DeleteRegistrationResponse is intentionally empty today: the AS returns 204 No Content with no body. Wrapped struct preserves the convention's (ctx, *Req) → (*Resp, error) shape and gives forward-compat headroom.
func DeleteRegistration ¶ added in v0.0.81
func DeleteRegistration(ctx context.Context, req *DeleteRegistrationRequest) (*DeleteRegistrationResponse, error)
DeleteRegistration performs an RFC 7592 §2.3 deletion. After it returns successfully the AS has removed the registration and invalidated the signing credentials, so any tokens already issued under this client_id will fail subsequent validation.
Maps server responses:
- 204 No Content → nil error + empty response struct
- 401 → ErrRegistrationUnauthorized
- others → generic error including status + body
type DeviceAuthorizationRequest ¶ added in v0.1.25
type DeviceAuthorizationRequest struct {
// ClientID is the OAuth client identifier. Required.
ClientID string
// ClientSecret authenticates a confidential client to the
// device_authorization endpoint. Empty for public clients.
ClientSecret string
// Scopes is the list of OAuth scopes to request. Empty omits the
// `scope` form value entirely (AS-default scope set).
Scopes []string
// Audience populates the RFC 8707 `audience` form value when set.
Audience string
}
DeviceAuthorizationRequest is the input to AuthClient.DeviceAuthorization.
type DeviceAuthorizationResponse ¶ added in v0.1.25
type DeviceAuthorizationResponse struct {
// DeviceCode is the high-entropy code the device polls with.
DeviceCode string `json:"device_code"`
// UserCode is the short, human-typeable code the user enters on the
// verification URI. Already formatted by the AS (e.g. "WDJB-MJHT").
UserCode string `json:"user_code"`
// VerificationURI is the URL the user opens to enter the user_code.
VerificationURI string `json:"verification_uri"`
// VerificationURIComplete is the convenience URL with the user_code
// pre-encoded as a query parameter (RFC 8628 §3.3.1). Empty when the
// AS doesn't advertise this field; clients SHOULD prefer it when
// rendering a QR code or opening a browser.
VerificationURIComplete string `json:"verification_uri_complete,omitempty"`
// ExpiresIn is the seconds until the device_code expires.
ExpiresIn int64 `json:"expires_in"`
// Interval is the AS-advised minimum polling interval in seconds.
// Clients MUST respect this; the AS may raise it via slow_down.
Interval int `json:"interval,omitempty"`
}
DeviceAuthorizationResponse is the AS's RFC 8628 §3.2 response.
type DiscoveryOption ¶ added in v0.0.56
type DiscoveryOption func(*discoveryConfig)
DiscoveryOption configures the discovery request.
func WithASCacheTTL ¶ added in v0.0.69
func WithASCacheTTL(ttl time.Duration) DiscoveryOption
WithASCacheTTL sets a custom TTL for cache writes during discovery. Only applies when a store is also provided via WithASMetadataStore. A TTL of 0 uses the store's default TTL.
func WithASMetadataStore ¶ added in v0.0.69
func WithASMetadataStore(store ASMetadataStore) DiscoveryOption
WithASMetadataStore enables caching of AS metadata via the given store. When set, DiscoverAS checks the store first and returns the cached value on hit. On miss, it fetches from the well-known endpoint and stores the result with the configured TTL (or the store's default).
Typical usage is to share a single store across multiple token sources in the same process:
cache := client.NewMemoryASMetadataStore(0)
md1, _ := client.DiscoverAS("https://auth.example.com", client.WithASMetadataStore(cache))
md2, _ := client.DiscoverAS("https://auth.example.com", client.WithASMetadataStore(cache))
// md2 returned from cache; no second HTTP fetch.
func WithHTTPClientForDiscovery ¶ added in v0.0.56
func WithHTTPClientForDiscovery(client *http.Client) DiscoveryOption
WithHTTPClientForDiscovery sets a custom HTTP client for the discovery request. Useful for testing (httptest) and custom TLS configuration.
type GetRegistrationRequest ¶ added in v0.0.81
type GetRegistrationRequest struct {
// RegistrationClientURI is the management endpoint returned at
// registration time (RFC 7592 §3 registration_client_uri).
RegistrationClientURI string
// RegistrationAccessToken authorizes calls to RegistrationClientURI
// (RFC 7592 §3).
RegistrationAccessToken string
// HTTPClient is used to make the request. nil → http.DefaultClient.
// Conceptually this is a transport option (analog of gRPC CallOption);
// it lives on the request struct so the method retains a strict
// (ctx, req) → (resp, err) signature.
HTTPClient *http.Client
}
GetRegistrationRequest is the input to GetRegistration.
type GetRegistrationResponse ¶ added in v0.0.81
type GetRegistrationResponse struct {
Registration *ClientRegistrationResponse
}
GetRegistrationResponse wraps the parsed registration metadata. Wrapped (rather than returning *ClientRegistrationResponse directly) for symmetry with the server-side ClientRegistrationManager interface and so future fields can be added without changing the method signature.
func GetRegistration ¶ added in v0.0.81
func GetRegistration(ctx context.Context, req *GetRegistrationRequest) (*GetRegistrationResponse, error)
GetRegistration performs an RFC 7592 §2.1 read of a previously-registered client.
The server is required to return 401 for any authentication failure; this function maps that case to ErrRegistrationUnauthorized so callers can branch on errors.Is. Other non-2xx responses surface as a generic error including status code and body for diagnostics.
type JwtBearerGrantRequest ¶ added in v0.1.5
type JwtBearerGrantRequest struct {
// ClientID identifies the client to the AS. Required.
ClientID string
// ClientSecret authenticates the client when ClientAssertion is nil.
ClientSecret string
// ClientAssertion, when non-nil, switches client authentication to
// the private_key_jwt path. The resulting `client_assertion` form
// field coexists with the bearer `assertion` below.
ClientAssertion *ClientAssertionConfig
// Assertion is the signed JWT presented as the authorization grant
// (RFC 7523 §2.1). Required. Issued out-of-band — typically the
// output of a prior token exchange or a trusted upstream IdP.
Assertion string
// Scope optionally narrows the requested scopes for the issued
// access token. Space-delimited on the wire per RFC 6749 §3.3.
Scope []string
// Resources are RFC 8707 resource indicators emitted as repeated
// `resource` form values.
Resources []string
}
JwtBearerGrantRequest models the inputs to RFC 7523 §2.1. Client authentication is negotiated separately via ClientID + (ClientSecret | ClientAssertion) — both authentication methods may coexist with the bearer Assertion in the same form payload.
type LoginRequest ¶ added in v0.1.8
type LoginRequest struct {
// Username is the resource-owner identifier (email / username).
Username string
// Password is the resource-owner secret.
Password string
// Scope is the requested OAuth scope (space-delimited).
Scope string
// ClientID identifies the client. Defaults to "cli" when empty.
ClientID string
// ClientSecret authenticates a confidential client to the token
// endpoint per RFC 6749 §2.3.1 (client_secret_basic / _post).
// Required by Keycloak (and most production ASes) when the client
// is registered as confidential; ignored on the legacy
// /auth/cli/token path. Empty for public clients (which use PKCE
// rather than a secret).
ClientSecret string
}
LoginRequest is the input to AuthClient.Login. It carries the resource-owner password credentials grant inputs (RFC 6749 §4.3). The ClientID defaults to "cli" if unset, matching the oneauth /api/token endpoint's expected value for first-party CLI clients.
type MemoryASMetadataStore ¶ added in v0.0.69
type MemoryASMetadataStore struct {
// contains filtered or unexported fields
}
MemoryASMetadataStore is an in-memory ASMetadataStore backed by a sync.RWMutex-protected map. Suitable for single-process deployments where all token sources run in the same binary.
Entries expire lazily on Get — there is no background eviction goroutine. Expired entries stay in the map until a Get or Put touches them, which is fine for bounded-size workloads (one entry per unique issuer URL).
func NewMemoryASMetadataStore ¶ added in v0.0.69
func NewMemoryASMetadataStore(defaultTTL time.Duration) *MemoryASMetadataStore
NewMemoryASMetadataStore creates a new in-memory AS metadata store. If defaultTTL is 0, DefaultASCacheTTL is used.
func (*MemoryASMetadataStore) Get ¶ added in v0.0.69
func (s *MemoryASMetadataStore) Get(issuer string) (*ASMetadata, bool)
Get returns cached metadata for an issuer if present and not expired. Expired entries are removed lazily on access.
func (*MemoryASMetadataStore) Put ¶ added in v0.0.69
func (s *MemoryASMetadataStore) Put(issuer string, md *ASMetadata, ttl time.Duration)
Put stores metadata for an issuer with the given TTL. If ttl is 0, the store's default TTL is used.
type OAuth2TokenRequest ¶
type OAuth2TokenRequest struct {
GrantType string `json:"grant_type"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
RefreshToken string `json:"refresh_token,omitempty"`
Scope string `json:"scope,omitempty"`
ClientID string `json:"client_id,omitempty"`
ClientSecret string `json:"client_secret,omitempty"`
Code string `json:"code,omitempty"` // For authorization_code grant
CodeVerifier string `json:"code_verifier,omitempty"` // PKCE verifier for authorization_code grant
RedirectURI string `json:"redirect_uri,omitempty"` // Redirect URI for authorization_code grant
}
OAuth2TokenRequest is the request body for token endpoint
type OAuth2TokenResponse ¶
type OAuth2TokenResponse struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
ExpiresIn int64 `json:"expires_in"`
RefreshToken string `json:"refresh_token,omitempty"`
Scope string `json:"scope,omitempty"`
AuthorizationDetails []any `json:"authorization_details,omitempty"` // RFC 9396 (raw JSON)
Error string `json:"error,omitempty"`
ErrorDesc string `json:"error_description,omitempty"`
}
OAuth2TokenResponse is the response from token endpoint
type PollDeviceTokenRequest ¶ added in v0.1.25
type PollDeviceTokenRequest struct {
// DeviceCode is the device_code from DeviceAuthorizationResponse.
DeviceCode string
// ClientID is the OAuth client identifier. Required.
ClientID string
// ClientSecret authenticates a confidential client (issue 266 — the
// AS REQUIRES this when the client is registered confidential).
ClientSecret string
}
PollDeviceTokenRequest is the input to AuthClient.PollDeviceToken.
type ProactiveRefresher ¶ added in v0.0.69
type ProactiveRefresher struct {
// Buffer is how long before token expiry to refresh. Must be positive
// to enable proactive refresh. A buffer of 30s means the refresh fires
// 30 seconds before the token would have expired reactively.
Buffer time.Duration
// contains filtered or unexported fields
}
ProactiveRefresher configures and manages background token refresh before expiry. It bundles the refresh policy (Buffer) with the runtime state needed to coordinate the background goroutine lifecycle.
type RefreshTokenRequest ¶ added in v0.1.16
type RefreshTokenRequest struct {
// ClientID identifies the client to the AS. Required.
ClientID string
// ClientSecret authenticates the client. Optional — public clients
// (PKCE-grant origin) leave it empty. When set, the auth method is
// negotiated from the AS metadata (client_secret_basic vs
// client_secret_post) per RFC 6749 §2.3, mirroring ClientCredentials.
ClientSecret string
// RefreshToken is the long-lived token issued by a prior grant.
// Required.
RefreshToken string
// Scopes, when non-empty, narrows the access-token scope to a subset
// of the original grant per RFC 6749 §6. Omitted from the form when
// empty (the AS reuses the original scope set).
Scopes []string
}
RefreshTokenRequest is the gRPC-shape input to RefreshToken — the caller-supplied refresh token plus the client identity that owned the original grant.
type ScopeAwareTokenSource ¶ added in v0.0.68
type ScopeAwareTokenSource interface {
TokenSource
TokenForScopes(scopes []string) (string, error)
}
ScopeAwareTokenSource extends TokenSource with scope step-up capability. When additional scopes are required, the cached token is invalidated and a new token is obtained with the merged scope set.
type ServerCredential ¶
type ServerCredential struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token,omitempty"`
TokenType string `json:"token_type,omitempty"`
UserID string `json:"user_id,omitempty"`
UserEmail string `json:"user_email,omitempty"`
Scope string `json:"scope,omitempty"`
AuthorizationDetails []core.AuthorizationDetail `json:"authorization_details,omitempty"` // RFC 9396
ExpiresAt time.Time `json:"expires_at"`
CreatedAt time.Time `json:"created_at"`
// ClientID is the client the token was issued to. Automatic refresh
// presents it, since the AS only redeems a refresh token for the
// client it was issued to (RFC 6749 §6). Empty in credentials stored
// by older SDK versions, where refresh falls back to "cli".
ClientID string `json:"client_id,omitempty"`
}
ServerCredential holds authentication info for a single server
func (*ServerCredential) HasRefreshToken ¶
func (c *ServerCredential) HasRefreshToken() bool
HasRefreshToken returns true if a refresh token is available
func (*ServerCredential) IsExpired ¶
func (c *ServerCredential) IsExpired() bool
IsExpired returns true if the access token has expired
func (*ServerCredential) IsExpiringSoon ¶
func (c *ServerCredential) IsExpiringSoon(within time.Duration) bool
IsExpiringSoon returns true if the token expires within the given duration
type TokenEndpointAuthMethod ¶ added in v0.0.65
type TokenEndpointAuthMethod string
TokenEndpointAuthMethod represents an OAuth 2.0 token endpoint authentication method as defined in RFC 6749 §2.3 and advertised by authorization servers via the "token_endpoint_auth_methods_supported" metadata field (RFC 8414 §2). The client uses SelectAuthMethod to negotiate which method to use based on what the AS supports.
See: https://www.rfc-editor.org/rfc/rfc6749#section-2.3 See: https://www.rfc-editor.org/rfc/rfc8414#section-2 (discovery metadata)
const ( // AuthMethodNone indicates a public client with no client secret. // Only the client_id is sent as a form body parameter. This is the // correct method for native/SPA clients using PKCE without a secret. AuthMethodNone TokenEndpointAuthMethod = "none" // AuthMethodClientSecretPost sends client_id and client_secret as // form body parameters in the token request. Less secure than Basic // because credentials appear in the request body (potentially logged // by proxies or WAFs), but required by some AS implementations. AuthMethodClientSecretPost TokenEndpointAuthMethod = "client_secret_post" // AuthMethodClientSecretBasic sends client credentials via the HTTP // Basic authentication scheme (RFC 7617) in the Authorization header: // "Authorization: Basic base64(client_id:client_secret)". This is the // RFC 6749 §2.3.1 default and preferred method because credentials // stay out of the request body. AuthMethodClientSecretBasic TokenEndpointAuthMethod = "client_secret_basic" )
const AuthMethodPrivateKeyJWT TokenEndpointAuthMethod = "private_key_jwt"
AuthMethodPrivateKeyJWT names the RFC 7521 §4.2 / RFC 7523 §2.2 / OIDC Core §9 token-endpoint client authentication method where the client signs a JWT with its registered private key and sends it as `client_assertion`. Strongest of the standard methods — there is no shared secret to leak.
func SelectAuthMethod ¶ added in v0.0.65
func SelectAuthMethod(clientSecret string, asMethods []string) TokenEndpointAuthMethod
SelectAuthMethod chooses the appropriate token endpoint authentication method based on the client's credentials and the AS's advertised token_endpoint_auth_methods_supported metadata.
Decision logic:
- No client secret → "none" (public client, e.g., PKCE-only native apps)
- AS advertises methods → pick best supported match, preferring client_secret_basic over client_secret_post (credentials in header are less likely to be logged than credentials in body)
- AS doesn't advertise methods (nil/empty) → default to client_secret_basic per RFC 6749 §2.3.1
- AS advertises only unknown methods (e.g., private_key_jwt) → fall back to client_secret_basic as a safe default
This function is used by both LoginWithBrowser (auth code + PKCE flow) and ClientCredentialsToken (machine-to-machine flow) to negotiate how credentials are sent to the token endpoint.
See: https://www.rfc-editor.org/rfc/rfc6749#section-2.3.1 See: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13#section-3.2.1 See: https://github.com/panyam/oneauth/issues/72
type TokenError ¶ added in v0.1.40
type TokenError struct {
Status int
Code string
Description string
// contains filtered or unexported fields
}
TokenError is a failed response from a token endpoint, returned by every grant this package drives: LoginWithBrowser's code exchange, Login, ClientCredentials, RefreshToken, JwtBearerGrant, TokenExchange and PollDeviceToken. DeviceAuthorization returns it too, since the RFC 8628 §3.2 device authorization endpoint answers with the same error object. It is usually wrapped, so match it with errors.As.
Code and Description are the RFC 6749 §5.2 error and error_description. Code is empty when the body is not a JSON error object (a proxy's 502 page, say); Status still carries the HTTP status. PollDeviceToken keeps returning its RFC 8628 sentinels (ErrAuthorizationPending and friends) for the four codes that drive its polling loop, and a *TokenError for the rest.
See: https://www.rfc-editor.org/rfc/rfc6749#section-5.2
func (*TokenError) Error ¶ added in v0.1.40
func (e *TokenError) Error() string
Error renders "<code>: <description>", or "token endpoint returned <status>" when the response carried no error code. Some calls keep an older wording of their own.
type TokenExchangeRequest ¶ added in v0.1.5
type TokenExchangeRequest struct {
// ClientID identifies the client to the AS. Required.
ClientID string
// ClientSecret authenticates the client when ClientAssertion is nil.
ClientSecret string
// ClientAssertion, when non-nil, switches client authentication to
// the private_key_jwt path (RFC 7521 §4.2 / RFC 7523 §2.2).
ClientAssertion *ClientAssertionConfig
// SubjectToken carries the security token representing the party on
// whose behalf the request is made. Required (RFC 8693 §2.1).
SubjectToken string
// SubjectTokenType identifies the kind of the SubjectToken (e.g.
// urn:ietf:params:oauth:token-type:id_token). Required.
SubjectTokenType string
// ActorToken is an optional second token identifying the acting
// party — used in delegation flows where the actor differs from the
// subject (RFC 8693 §1.2).
ActorToken string
// ActorTokenType identifies the kind of the ActorToken. Required
// when ActorToken is set.
ActorTokenType string
// RequestedTokenType identifies the kind of token the caller wants
// the AS to issue. Optional — when omitted the AS chooses.
RequestedTokenType string
// Audience names the relying parties intended to consume the
// issued token (RFC 8693 §2.1). Emitted as repeated `audience`
// form values when more than one is provided.
Audience []string
// Resource is the RFC 8707 resource indicator. Emitted as repeated
// `resource` form values per §2.
Resource []string
// Scope requests specific scopes for the issued token. Encoded as a
// single space-delimited `scope` form value per RFC 6749 §3.3.
Scope []string
}
TokenExchangeRequest models the inputs to RFC 8693 §2.1. SubjectToken and SubjectTokenType are required; everything else is optional and is omitted from the wire request when unset.
type TokenExchangeResponse ¶ added in v0.1.5
type TokenExchangeResponse struct {
AccessToken string
IssuedTokenType string
TokenType string
ExpiresIn int
RefreshToken string
Scope []string
}
TokenExchangeResponse is the parsed RFC 8693 §2.2 response. The wire format is a JSON object with snake_case keys; `Scope` is split from the space-delimited string into a slice for convenience.
type TokenSource ¶ added in v0.0.68
TokenSource provides OAuth2 access tokens. This interface matches mcpkit/core.TokenSource by structural typing — no cross-module import needed.
type UpdateRegistrationRequest ¶ added in v0.0.81
type UpdateRegistrationRequest struct {
RegistrationClientURI string
RegistrationAccessToken string
// ClientID is the client's existing client_id. Required by RFC 7592 §2.2;
// auto-filled into Metadata.ClientID by the SDK before sending if the
// caller hasn't already set it.
ClientID string
// Metadata is the full RFC 7591/7592 client metadata that will replace
// the existing registration. Treated as a full replacement (not PATCH).
Metadata ClientRegistrationRequest
// HTTPClient — see GetRegistrationRequest for rationale.
HTTPClient *http.Client
}
UpdateRegistrationRequest is the input to UpdateRegistration.
type UpdateRegistrationResponse ¶ added in v0.0.81
type UpdateRegistrationResponse struct {
Registration *ClientRegistrationResponse
}
UpdateRegistrationResponse wraps the post-update registration. Registration includes the rotated registration_access_token, which supersedes the one in the request. Callers MUST persist the new token before discarding the old one.
func UpdateRegistration ¶ added in v0.0.81
func UpdateRegistration(ctx context.Context, req *UpdateRegistrationRequest) (*UpdateRegistrationResponse, error)
UpdateRegistration performs an RFC 7592 §2.2 full-replace update.
On success the AS rotates the registration_access_token; the new token surfaces on the response.
Maps server responses:
- 200 OK → returns the parsed response (with the rotated token)
- 401 → ErrRegistrationUnauthorized
- 400 → returns a generic error including the AS's error_description
- others → generic error including status + body