Documentation
¶
Overview ¶
Package oauth provides helpers for OAuth 2.0 authorization flows, token lifecycle management, and server metadata discovery.
Discovery and registration ¶
Discover fetches OAuth 2.0 Authorization Server Metadata (RFC 8414) or OpenID Connect Discovery metadata (OIDC Discovery 1.0). Pass any URL on the server — the function walks up the path until it finds the well-known document.
Register performs RFC 7591 Dynamic Client Registration and returns an OAuthCredentials with the assigned ClientID and Secret embedded, ready to pass straight into an authorization function.
Authorization flows ¶
- AuthorizeWithBrowser — Authorization Code + PKCE via loopback redirect (RFC 6749)
- AuthorizeWithCode — Authorization Code + PKCE via manual code paste (RFC 6749)
- AuthorizeWithDevice — Device Authorization Grant (RFC 8628)
- AuthorizeWithCredentials — Client Credentials grant (RFC 6749 §4.4)
Token lifecycle ¶
- OAuthCredentials.Refresh — exchange a refresh token for a new access token
- OAuthCredentials.Revoke — revoke access and refresh tokens (RFC 7009)
- OAuthCredentials.Introspect — query token metadata from the server (RFC 7662)
- OAuthCredentials.Valid — local check that the token is set and non-expired
Custom HTTP client ¶
Every function that makes a network call honours a custom *http.Client injected via context:
ctx = context.WithValue(ctx, oauth2.HTTPClient, myClient)
Index ¶
- Constants
- Variables
- type DevicePromptFunc
- type IntrospectionResponse
- type OAuth
- type OAuthCredentials
- func AuthorizeWithBrowser(ctx context.Context, creds *OAuthCredentials, listener net.Listener, ...) (*OAuthCredentials, error)
- func AuthorizeWithCode(ctx context.Context, creds *OAuthCredentials, prompt PromptFunc, ...) (*OAuthCredentials, error)
- func AuthorizeWithCredentials(ctx context.Context, creds *OAuthCredentials, scopes ...string) (*OAuthCredentials, error)
- func AuthorizeWithDevice(ctx context.Context, creds *OAuthCredentials, prompt DevicePromptFunc, ...) (*OAuthCredentials, error)
- func Register(ctx context.Context, metadata *OAuthMetadata, clientName string, ...) (*OAuthCredentials, error)
- type OAuthFlow
- type OAuthMetadata
- func (m *OAuthMetadata) OAuthEndpoint() oauth2.Endpoint
- func (m *OAuthMetadata) SupportsDeviceFlow() bool
- func (m *OAuthMetadata) SupportsFlow(flow OAuthFlow) error
- func (m *OAuthMetadata) SupportsGrantType(flow OAuthFlow) bool
- func (m *OAuthMetadata) SupportsIntrospection() bool
- func (m *OAuthMetadata) SupportsPKCE() bool
- func (m *OAuthMetadata) SupportsRegistration() bool
- func (m *OAuthMetadata) SupportsRevocation() bool
- func (m *OAuthMetadata) SupportsS256() bool
- func (m *OAuthMetadata) ValidateScopes(scopes ...string) error
- type OpenFunc
- type PromptFunc
- type ProtectedResourceMetadata
Constants ¶
const ( // OAuthWellKnownPath is the standard OAuth 2.0 Authorization Server Metadata endpoint (RFC 8414). OAuthWellKnownPath = "/.well-known/oauth-authorization-server" // OIDCWellKnownPath is the OpenID Connect Discovery endpoint (OpenID Connect Discovery 1.0). OIDCWellKnownPath = "/.well-known/openid-configuration" // OAuthProtectedResourcePath is the Protected Resource Metadata endpoint (RFC 9728). OAuthProtectedResourcePath = "/.well-known/oauth-protected-resource" )
Variables ¶
var (
ErrInvalidToken = errors.New("Invalid token")
)
Functions ¶
This section is empty.
Types ¶
type DevicePromptFunc ¶
DevicePromptFunc is called with the user code and verification URI once the device authorization request succeeds. The implementation should display them to the user — e.g. print to stdout or show a QR code — and then return. AuthorizeWithDevice polls for the token automatically after this function returns.
type IntrospectionResponse ¶
type IntrospectionResponse struct {
// Active indicates whether the token is currently active. A false value
// means the token is expired, revoked, or otherwise invalid.
Active bool `json:"active"`
// Scope is a space-separated list of scopes associated with the token.
Scope string `json:"scope,omitempty"`
// ClientID is the client identifier for the OAuth client that requested
// the token.
ClientID string `json:"client_id,omitempty"`
// Username is a human-readable identifier for the resource owner.
Username string `json:"username,omitempty"`
// TokenType is the type of the token (e.g. "Bearer").
TokenType string `json:"token_type,omitempty"`
// Expiry is when the token expires (from the exp claim). Zero means absent.
Expiry time.Time `json:"-"`
// NotBefore is the earliest time the token is valid (from the nbf claim). Zero means absent.
NotBefore time.Time `json:"-"`
// IssuedAt is when the token was issued (from the iat claim). Zero means absent.
IssuedAt time.Time `json:"-"`
// Audience is the intended recipients of the token (from the aud claim).
// Per RFC 7519 §4.1.3 aud may be a single string or an array; both are
// normalised to a slice here.
Audience []string `json:"-"`
// Subject is the subject of the token (typically the user ID).
Subject string `json:"sub,omitempty"`
// Issuer identifies the principal that issued the token.
Issuer string `json:"iss,omitempty"`
// JWTID is a unique identifier for the token.
JWTID string `json:"jti,omitempty"`
}
IntrospectionResponse represents an OAuth 2.0 Token Introspection response (RFC 7662 §2.2). The Active field is always populated; all other fields are optional and depend on what the server chooses to return.
The Unix timestamp claims (exp, nbf, iat) and the aud claim are decoded automatically; use the typed fields (Expiry, NotBefore, IssuedAt, Audience) rather than the raw JSON fields.
func (*IntrospectionResponse) UnmarshalJSON ¶
func (r *IntrospectionResponse) UnmarshalJSON(data []byte) error
UnmarshalJSON implements json.Unmarshaler for IntrospectionResponse. It handles the Unix timestamp fields (exp, nbf, iat) and the polymorphic aud claim (single string or array) that cannot be decoded with plain tags.
type OAuth ¶ added in v1.4.1
type OAuth struct {
oauth2.Token
// ClientID is the OAuth client ID used to obtain this token.
ClientID string `json:"client_id"`
// ClientSecret is the OAuth client secret, if any (for confidential clients).
// Needed for token refresh with servers that require client authentication.
ClientSecret string `json:"client_secret,omitempty"`
// Endpoint is the server endpoint used for discovery
Endpoint string `json:"endpoint"`
// TokenURL is the OAuth token endpoint URL (used for refresh without re-discovery).
TokenURL string `json:"token_url"`
}
func NewConfig ¶ added in v1.4.1
NewConfig creates a new OAuth configuration from the given oauth2.Config.
type OAuthCredentials ¶
type OAuthCredentials struct {
*oauth2.Token
// ClientID is the OAuth client ID used to obtain this token.
ClientID string `json:"client_id"`
// ClientSecret is the OAuth client secret, if any (for confidential clients).
// Needed for token refresh with servers that require client authentication.
ClientSecret string `json:"client_secret,omitempty"`
// TokenURL is the OAuth token endpoint URL (used for refresh without re-discovery).
TokenURL string `json:"token_url"`
// Metadata is the server's OAuth 2.0 Authorization Server Metadata.
// It is populated by Register and carried through into the authorize functions
// so callers do not need to pass it separately.
Metadata *OAuthMetadata `json:"metadata,omitempty"`
// RedirectURI is the redirect URI sent in the authorization request and token
// exchange. Defaults to empty (server default). Set to
// "urn:ietf:wg:oauth:2.0:oob" for native/CLI apps that display the code
// in the browser rather than redirecting to a callback server.
RedirectURI string `json:"redirect_uri,omitempty"`
// OnRefresh is an optional callback invoked after a new token is obtained
// from the server (i.e. when the old token was expired or about to expire).
// It is not called when the existing token is still valid.
// Use it to persist updated credentials — especially important when the
// server rotates refresh tokens on each use.
//
// If the callback returns an error, Refresh propagates it.
// OnRefresh is not serialised to JSON.
OnRefresh func(*OAuthCredentials) error `json:"-"`
}
OAuthCredentials bundles an OAuth token with the metadata needed to refresh or reuse it later without re-discovering or re-registering.
func AuthorizeWithBrowser ¶
func AuthorizeWithBrowser(ctx context.Context, creds *OAuthCredentials, listener net.Listener, open OpenFunc, scopes ...string) (*OAuthCredentials, error)
AuthorizeWithBrowser performs an OAuth 2.0 Authorization Code flow with PKCE using a loopback redirect. It:
- Derives redirect_uri from the listener address (http://localhost:PORT/callback)
- Starts a temporary HTTP server on the listener
- Calls open(authURL) — typically to launch a browser
- Waits for the provider to redirect back with the authorization code
- Exchanges the code for tokens and shuts the server down
The creds parameter must have Metadata and ClientID set (e.g. obtained from Register or constructed manually). If no scopes are provided, the scope parameter is omitted and the server applies its own defaults (RFC 6749 §3.3). The caller is responsible for creating the listener, e.g.:
ln, _ := net.Listen("tcp", "localhost:0") // random port
To use a custom HTTP client for the token exchange, inject it into the context:
ctx = context.WithValue(ctx, oauth2.HTTPClient, myClient)
func AuthorizeWithCode ¶
func AuthorizeWithCode(ctx context.Context, creds *OAuthCredentials, prompt PromptFunc, scopes ...string) (*OAuthCredentials, error)
AuthorizeWithCode performs an interactive OAuth 2.0 Authorization Code flow with PKCE. The creds parameter must have Metadata and ClientID set (e.g. obtained from Register or constructed manually). The prompt callback is called with the authorization URL; it should present the URL to the user and return the authorization code they paste back. If no scopes are provided, the scope parameter is omitted and the server applies its own defaults (RFC 6749 §3.3). The returned credentials carry the token and preserve Metadata for subsequent calls.
To use a custom HTTP client for the token exchange, inject it into the context with:
ctx = context.WithValue(ctx, oauth2.HTTPClient, myClient)
func AuthorizeWithCredentials ¶
func AuthorizeWithCredentials(ctx context.Context, creds *OAuthCredentials, scopes ...string) (*OAuthCredentials, error)
AuthorizeWithCredentials performs an OAuth 2.0 Client Credentials grant (RFC 6749 §4.4). This is the machine-to-machine flow: no user interaction is required. The creds parameter must have Metadata, ClientID, and ClientSecret set.
Scopes are optional; pass none to request the server's default scopes. The returned credentials contain the access token. Refresh tokens are not issued by servers for this grant type — call AuthorizeWithCredentials again when the token expires, or use the returned OnRefresh pattern with a wrapper.
To use a custom HTTP client, inject it into the context:
ctx = context.WithValue(ctx, oauth2.HTTPClient, myClient)
func AuthorizeWithDevice ¶
func AuthorizeWithDevice(ctx context.Context, creds *OAuthCredentials, prompt DevicePromptFunc, scopes ...string) (*OAuthCredentials, error)
AuthorizeWithDevice performs an OAuth 2.0 Device Authorization Grant (RFC 8628). It is suited to CLI tools and devices that cannot open a browser themselves.
- Requests a device code from the server.
- Calls prompt with the user_code and verification_uri.
- Polls the token endpoint until the user completes authorization or the code expires.
The creds parameter must have Metadata and ClientID set (e.g. obtained from Register or constructed manually). If no scopes are provided, "openid" is requested by default.
To use a custom HTTP client, inject it into the context:
ctx = context.WithValue(ctx, oauth2.HTTPClient, myClient)
func Register ¶
func Register(ctx context.Context, metadata *OAuthMetadata, clientName string, redirectURIs ...string) (*OAuthCredentials, error)
Register performs RFC 7591 Dynamic Client Registration against the server described by metadata. It registers a new client with the given name and returns credentials containing the assigned ClientID and ClientSecret (if any). The returned OAuthCredentials has no token yet — pass it to AuthorizeWithCode, AuthorizeWithBrowser, or AuthorizeWithDeviceCode to obtain one.
redirectURIs are required by some servers (e.g. for authorization_code flows); omit them for device or client_credentials flows if the server allows it.
To use a custom HTTP client, inject it into the context:
ctx = context.WithValue(ctx, oauth2.HTTPClient, myClient)
func (*OAuthCredentials) Introspect ¶
func (creds *OAuthCredentials) Introspect(ctx context.Context) (*IntrospectionResponse, error)
Introspect queries the server's introspection endpoint (RFC 7662) for metadata about the current access token. It returns an IntrospectionResponse whose Active field indicates whether the token is currently valid.
Returns an error if the server does not advertise an introspection_endpoint, if the credentials have no token, or if the network call fails.
To use a custom HTTP client, inject it into the context:
ctx = context.WithValue(ctx, oauth2.HTTPClient, myClient)
func (*OAuthCredentials) Refresh ¶
func (creds *OAuthCredentials) Refresh(ctx context.Context) error
Refresh obtains a fresh access token. The strategy depends on the grant type:
- If the token is still valid, it is returned as-is (no network call).
- If a refresh token is present, it is exchanged for a new access token (RFC 6749 §6). The oauth2 library applies a 10-second expiry buffer.
- If no refresh token is present but a client secret is set, the Client Credentials grant (RFC 6749 §4.4) is used to obtain a fresh token. This is the normal path for machine-to-machine tokens, which servers never issue refresh tokens for.
When a new token is fetched from the server, OnRefresh (if set) is called with the updated credentials. Use it to persist rotated refresh tokens.
func (*OAuthCredentials) Revoke ¶
func (creds *OAuthCredentials) Revoke(ctx context.Context) error
Revoke revokes the current access token (and optionally the refresh token) by calling the server's revocation endpoint (RFC 7009).
The token to revoke is chosen as follows:
- If the access token is set, it is revoked first.
- If the refresh token is set, it is revoked as well.
After a successful call both tokens are cleared on the credentials struct so accidental reuse is prevented.
Revoke is a no-op (returns nil) when neither token is set or when the server does not advertise a revocation_endpoint.
To use a custom HTTP client, inject it into the context:
ctx = context.WithValue(ctx, oauth2.HTTPClient, myClient)
func (*OAuthCredentials) Valid ¶
func (creds *OAuthCredentials) Valid() bool
Valid returns true if the credentials contain a non-nil, non-expired token.
type OAuthFlow ¶
type OAuthFlow string
OAuthFlow represents an OAuth 2.0 grant type.
const ( // OAuthFlowAuthorizationCode is the Authorization Code grant (RFC 6749 §4.1). OAuthFlowAuthorizationCode OAuthFlow = "authorization_code" // OAuthFlowDeviceCode is the Device Authorization grant (RFC 8628). OAuthFlowDeviceCode OAuthFlow = "urn:ietf:params:oauth:grant-type:device_code" // OAuthFlowClientCredentials is the Client Credentials grant (RFC 6749 §4.4). OAuthFlowClientCredentials OAuthFlow = "client_credentials" // OAuthFlowRefreshToken is the Refresh Token grant (RFC 6749 §6). OAuthFlowRefreshToken OAuthFlow = "refresh_token" )
type OAuthMetadata ¶
type OAuthMetadata struct {
Issuer string `json:"issuer"`
AuthorizationEndpoint string `json:"authorization_endpoint"`
TokenEndpoint string `json:"token_endpoint"`
DeviceAuthorizationEndpoint string `json:"device_authorization_endpoint,omitempty"`
RegistrationEndpoint string `json:"registration_endpoint,omitempty"`
JwksURI string `json:"jwks_uri,omitempty"`
ResponseTypesSupported []string `json:"response_types_supported,omitempty"`
ResponseModesSupported []string `json:"response_modes_supported,omitempty"`
GrantTypesSupported []string `json:"grant_types_supported,omitempty"`
TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported,omitempty"`
ScopesSupported []string `json:"scopes_supported,omitempty"`
CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported,omitempty"`
RevocationEndpoint string `json:"revocation_endpoint,omitempty"`
IntrospectionEndpoint string `json:"introspection_endpoint,omitempty"`
}
OAuthMetadata represents OAuth 2.0 Authorization Server Metadata (RFC 8414).
func Discover ¶
func Discover(ctx context.Context, endpoint string) (*OAuthMetadata, error)
Discover fetches OAuth 2.0 Authorization Server Metadata from the well-known endpoint on the server. It tries RFC 8414 root paths first, then falls back to path-relative discovery (e.g., Keycloak realms).
func SynthesizeMetadata ¶ added in v1.4.2
func SynthesizeMetadata(issuerURL string) *OAuthMetadata
SynthesizeMetadata constructs a minimal OAuthMetadata for an authorization server that does not publish an RFC 8414 discovery document. It derives the endpoints by appending standard path suffixes to issuerURL, following the fallback convention described in the MCP OAuth specification:
authorization_endpoint = issuerURL + "/authorize" token_endpoint = issuerURL + "/token"
This is suitable for legacy OAuth 2.0 servers such as GitHub (https://github.com/login/oauth) that predate RFC 8414.
func (*OAuthMetadata) OAuthEndpoint ¶
func (m *OAuthMetadata) OAuthEndpoint() oauth2.Endpoint
OAuthEndpoint returns an oauth2.Endpoint built from the metadata. The AuthStyle is derived from token_endpoint_auth_methods_supported (RFC 8414):
- "client_secret_post" only → AuthStyleInParams (body)
- "client_secret_basic" only → AuthStyleInHeader (HTTP Basic)
- both or neither → AuthStyleAutoDetect
func (*OAuthMetadata) SupportsDeviceFlow ¶
func (m *OAuthMetadata) SupportsDeviceFlow() bool
SupportsDeviceFlow returns true if the server supports the device authorization grant. The presence of device_authorization_endpoint is the primary indicator per RFC 8628.
func (*OAuthMetadata) SupportsFlow ¶
func (m *OAuthMetadata) SupportsFlow(flow OAuthFlow) error
SupportsFlow returns an error if the metadata is missing fields required for the given grant type flow. Supported grant types and their requirements:
- OAuthFlowAuthorizationCode: authorization_endpoint + token_endpoint
- OAuthFlowDeviceCode: device_authorization_endpoint + token_endpoint
- OAuthFlowClientCredentials, OAuthFlowRefreshToken, others: token_endpoint only
func (*OAuthMetadata) SupportsGrantType ¶
func (m *OAuthMetadata) SupportsGrantType(flow OAuthFlow) bool
SupportsGrantType returns true if the server supports the given grant type. Per RFC 8414, grant_types_supported is optional — when omitted, true is returned to avoid blocking flows that might still be supported.
func (*OAuthMetadata) SupportsIntrospection ¶
func (m *OAuthMetadata) SupportsIntrospection() bool
SupportsIntrospection returns true if the server supports token introspection (RFC 7662).
func (*OAuthMetadata) SupportsPKCE ¶
func (m *OAuthMetadata) SupportsPKCE() bool
SupportsPKCE returns true if the server supports PKCE.
func (*OAuthMetadata) SupportsRegistration ¶
func (m *OAuthMetadata) SupportsRegistration() bool
SupportsRegistration returns true if the server supports dynamic client registration (RFC 7591).
func (*OAuthMetadata) SupportsRevocation ¶
func (m *OAuthMetadata) SupportsRevocation() bool
SupportsRevocation returns true if the server supports token revocation (RFC 7009).
func (*OAuthMetadata) SupportsS256 ¶
func (m *OAuthMetadata) SupportsS256() bool
SupportsS256 returns true if the server supports the S256 challenge method.
func (*OAuthMetadata) ValidateScopes ¶
func (m *OAuthMetadata) ValidateScopes(scopes ...string) error
ValidateScopes checks that every requested scope is listed in scopes_supported. Per RFC 8414 the field is optional — when the server does not advertise it, all scopes are accepted and nil is returned. An empty scopes argument always returns nil.
type OpenFunc ¶
OpenFunc is called with the authorization URL and should open it in a browser. On macOS the caller can use:
func(u string) error { return exec.Command("open", u).Start() }
type PromptFunc ¶
PromptFunc is called with the authorization URL. The implementation should display it to the user, obtain the authorization code (e.g. by opening a browser or printing the URL and reading from stdin), and return it.
type ProtectedResourceMetadata ¶ added in v1.4.2
type ProtectedResourceMetadata struct {
Resource string `json:"resource"`
AuthorizationServers []string `json:"authorization_servers"`
ScopesSupported []string `json:"scopes_supported,omitempty"`
BearerMethodsSupported []string `json:"bearer_methods_supported,omitempty"`
ResourceName string `json:"resource_name,omitempty"`
}
ProtectedResourceMetadata represents OAuth 2.0 Protected Resource Metadata (RFC 9728). It is returned by a resource server's well-known endpoint and lists the authorization servers that protect the resource.