Documentation
¶
Overview ¶
Package upstream provides types and implementations for upstream Identity Provider communication in the OAuth authorization server.
Architecture ¶
The package is designed around a core OAuth2Provider interface that abstracts upstream IDP operations. The interface captures essential OAuth/OIDC operations without leaking implementation details:
- Type: Returns the provider type identifier
- AuthorizationURL: Build redirect URL for user authentication
- ExchangeCode: Exchange authorization code for tokens
- RefreshTokens: Refresh expired tokens
- ResolveIdentity: Resolve user identity from tokens
- FetchUserInfo: Fetch user claims
Type Hierarchy ¶
OAuth2Provider (interface)
├── BaseOAuth2Provider (concrete - pure OAuth 2.0, uses UserInfo for identity)
└── OIDCProviderImpl (concrete - OIDC with discovery, validates ID tokens for identity)
Value Objects ¶
- Tokens: Token response from upstream IDP
- UserInfo: User claims from UserInfo endpoint
- OAuth2Config: Configuration for OAuth 2.0 providers
Usage ¶
config := &upstream.OAuth2Config{
CommonOAuthConfig: upstream.CommonOAuthConfig{
ClientID: "your-client-id",
ClientSecret: "your-client-secret",
RedirectURI: "https://your-app.com/callback",
Scopes: []string{"read", "write"},
},
AuthorizationEndpoint: "https://provider.com/authorize",
TokenEndpoint: "https://provider.com/token",
UserInfo: &upstream.UserInfoConfig{
EndpointURL: "https://provider.com/userinfo",
},
}
provider, err := upstream.NewOAuth2Provider(config)
if err != nil {
return err
}
// Build authorization URL
authURL, err := provider.AuthorizationURL(state, pkceChallenge)
// After callback, exchange code for tokens
tokens, err := provider.ExchangeCode(ctx, code, pkceVerifier)
// Resolve user identity
subject, err := provider.ResolveIdentity(ctx, tokens, "")
Extensibility ¶
To add a new IDP type (e.g., SAML), implement the OAuth2Provider interface.
UserInfo Extensibility ¶
The package supports flexible UserInfo fetching through the OAuth2Provider interface's FetchUserInfo method and UserInfoConfig. This enables:
- Custom field mapping for non-standard provider responses
- Additional headers for provider-specific requirements
All OAuth2Provider implementations support FetchUserInfo directly:
userInfo, err := provider.FetchUserInfo(ctx, accessToken)
For custom provider configuration, use UserInfoConfig:
config := &upstream.UserInfoConfig{
EndpointURL: "https://api.example.com/user",
HTTPMethod: "GET", // or "POST" per OIDC Core Section 5.3.1
FieldMapping: &upstream.UserInfoFieldMapping{
SubjectFields: []string{"user_id"}, // custom field for non-OIDC providers
},
}
Index ¶
- Variables
- func ResolveField(claims map[string]any, fields []string) string
- type AuthorizationOption
- type BaseOAuth2Provider
- func (p *BaseOAuth2Provider) AuthorizationURL(state, codeChallenge string, opts ...AuthorizationOption) (string, error)
- func (p *BaseOAuth2Provider) ExchangeCode(ctx context.Context, code, codeVerifier string) (*Tokens, error)
- func (p *BaseOAuth2Provider) FetchUserInfo(ctx context.Context, accessToken string) (*UserInfo, error)
- func (p *BaseOAuth2Provider) RefreshTokens(ctx context.Context, refreshToken string) (*Tokens, error)
- func (p *BaseOAuth2Provider) ResolveIdentity(ctx context.Context, tokens *Tokens, _ string) (string, error)
- func (*BaseOAuth2Provider) Type() ProviderType
- type CommonOAuthConfig
- type OAuth2Config
- type OAuth2Provider
- type OAuth2ProviderOption
- type OIDCConfig
- type OIDCProviderImpl
- type OIDCProviderOption
- type ProviderType
- type Tokens
- type UserInfo
- type UserInfoConfig
- type UserInfoFieldMapping
- func (m *UserInfoFieldMapping) GetEmailFields() []string
- func (m *UserInfoFieldMapping) GetNameFields() []string
- func (m *UserInfoFieldMapping) GetSubjectFields() []string
- func (m *UserInfoFieldMapping) ResolveEmail(claims map[string]any) string
- func (m *UserInfoFieldMapping) ResolveName(claims map[string]any) string
- func (m *UserInfoFieldMapping) ResolveSubject(claims map[string]any) (string, error)
Constants ¶
This section is empty.
Variables ¶
var ( // DefaultSubjectFields is the default field name for subject resolution. DefaultSubjectFields = []string{"sub"} // DefaultNameFields is the default field name for name resolution. DefaultNameFields = []string{"name"} // DefaultEmailFields is the default field name for email resolution. DefaultEmailFields = []string{"email"} )
Default field names for standard OIDC claims.
var ErrIdentityResolutionFailed = errors.New("failed to resolve user identity")
ErrIdentityResolutionFailed indicates identity could not be determined.
var ErrNonceMismatch = errors.New("ID token nonce does not match expected value")
ErrNonceMismatch is returned when the nonce claim in the ID token does not match the expected nonce from the authorization request.
Functions ¶
func ResolveField ¶ added in v0.8.3
ResolveField attempts to extract a string value from claims using an ordered list of field names. Returns the first non-empty string value found, or an empty string if none found. This function handles type conversion gracefully - non-string values are skipped.
Types ¶
type AuthorizationOption ¶ added in v0.7.2
type AuthorizationOption func(*authorizationOptions)
AuthorizationOption configures authorization URL generation.
func WithAdditionalParams ¶ added in v0.7.2
func WithAdditionalParams(params map[string]string) AuthorizationOption
WithAdditionalParams adds custom parameters to the authorization URL.
type BaseOAuth2Provider ¶ added in v0.7.2
type BaseOAuth2Provider struct {
// contains filtered or unexported fields
}
BaseOAuth2Provider implements OAuth 2.0 flows for pure OAuth 2.0 providers. This can be used standalone for OAuth 2.0 providers without OIDC support, or embedded by OIDCProvider to share common OAuth 2.0 logic.
func NewOAuth2Provider ¶ added in v0.7.2
func NewOAuth2Provider(config *OAuth2Config, opts ...OAuth2ProviderOption) (*BaseOAuth2Provider, error)
NewOAuth2Provider creates a new pure OAuth 2.0 provider. Use this for providers that don't support OIDC discovery. The config must include AuthorizationEndpoint and TokenEndpoint.
func (*BaseOAuth2Provider) AuthorizationURL ¶ added in v0.7.2
func (p *BaseOAuth2Provider) AuthorizationURL(state, codeChallenge string, opts ...AuthorizationOption) (string, error)
AuthorizationURL builds the URL to redirect the user to the upstream IDP.
func (*BaseOAuth2Provider) ExchangeCode ¶ added in v0.7.2
func (p *BaseOAuth2Provider) ExchangeCode(ctx context.Context, code, codeVerifier string) (*Tokens, error)
ExchangeCode exchanges an authorization code for tokens with the upstream IDP.
func (*BaseOAuth2Provider) FetchUserInfo ¶ added in v0.8.0
func (p *BaseOAuth2Provider) FetchUserInfo(ctx context.Context, accessToken string) (*UserInfo, error)
FetchUserInfo fetches user information from the configured UserInfo endpoint. Returns an error if no UserInfo endpoint is configured. The field mapping from UserInfoConfig.FieldMapping is used to extract claims from non-standard provider responses.
func (*BaseOAuth2Provider) RefreshTokens ¶ added in v0.7.2
func (p *BaseOAuth2Provider) RefreshTokens(ctx context.Context, refreshToken string) (*Tokens, error)
RefreshTokens refreshes the upstream IDP tokens.
func (*BaseOAuth2Provider) ResolveIdentity ¶ added in v0.8.0
func (p *BaseOAuth2Provider) ResolveIdentity(ctx context.Context, tokens *Tokens, _ string) (string, error)
ResolveIdentity resolves the user's identity by fetching UserInfo. For pure OAuth2 providers, this fetches user information from the configured endpoint. OIDC-specific validation (ID token nonce, subject matching) will be added when OIDCProvider is implemented.
func (*BaseOAuth2Provider) Type ¶ added in v0.7.2
func (*BaseOAuth2Provider) Type() ProviderType
Type returns the provider type.
type CommonOAuthConfig ¶ added in v0.7.2
type CommonOAuthConfig struct {
// ClientID is the OAuth client ID registered with the upstream IDP.
ClientID string `json:"client_id" yaml:"client_id"`
// ClientSecret is the OAuth client secret registered with the upstream IDP.
// Optional for public clients (RFC 6749 Section 2.1) which authenticate using
// PKCE instead of a client secret. Required for confidential clients.
ClientSecret string `json:"client_secret,omitempty" yaml:"client_secret,omitempty"`
// Scopes are the OAuth scopes to request from the upstream IDP.
Scopes []string `json:"scopes,omitempty" yaml:"scopes,omitempty"`
// RedirectURI is the callback URL where the upstream IDP will redirect
// after authentication.
RedirectURI string `json:"redirect_uri" yaml:"redirect_uri"`
}
CommonOAuthConfig contains fields shared by all OAuth provider types. This provides compile-time type safety by separating OIDC and OAuth2 configuration.
func (*CommonOAuthConfig) Validate ¶ added in v0.7.2
func (c *CommonOAuthConfig) Validate() error
Validate checks that CommonOAuthConfig has all required fields.
type OAuth2Config ¶ added in v0.7.2
type OAuth2Config struct {
CommonOAuthConfig `yaml:",inline"`
// AuthorizationEndpoint is the URL for the OAuth authorization endpoint.
AuthorizationEndpoint string `json:"authorization_endpoint" yaml:"authorization_endpoint"`
// TokenEndpoint is the URL for the OAuth token endpoint.
TokenEndpoint string `json:"token_endpoint" yaml:"token_endpoint"`
// UserInfo contains configuration for fetching user information (optional).
// When nil, the provider does not support UserInfo fetching.
UserInfo *UserInfoConfig `json:"userinfo,omitempty" yaml:"userinfo,omitempty"`
}
OAuth2Config contains configuration for pure OAuth 2.0 providers without OIDC discovery.
func (*OAuth2Config) Validate ¶ added in v0.7.2
func (c *OAuth2Config) Validate() error
Validate checks that OAuth2Config has all required fields.
type OAuth2Provider ¶ added in v0.7.2
type OAuth2Provider interface {
// Type returns the provider type.
Type() ProviderType
// AuthorizationURL builds the URL to redirect the user to the upstream IDP.
// state: our internal state to correlate callback
// codeChallenge: PKCE challenge to send to upstream (if supported)
// opts: optional configuration such as nonce or additional parameters
AuthorizationURL(state, codeChallenge string, opts ...AuthorizationOption) (string, error)
// ExchangeCode exchanges an authorization code for tokens with the upstream IDP.
ExchangeCode(ctx context.Context, code, codeVerifier string) (*Tokens, error)
// RefreshTokens refreshes the upstream IDP tokens.
RefreshTokens(ctx context.Context, refreshToken string) (*Tokens, error)
// ResolveIdentity validates tokens and returns the canonical subject.
// For OIDC providers, it validates the ID token and nonce (ID token required).
// For pure OAuth2 providers, it fetches UserInfo to resolve identity.
ResolveIdentity(ctx context.Context, tokens *Tokens, nonce string) (subject string, err error)
// FetchUserInfo retrieves user information using the provided access token.
// Returns an error if the provider is not configured for UserInfo fetching.
FetchUserInfo(ctx context.Context, accessToken string) (*UserInfo, error)
}
OAuth2Provider handles communication with an upstream Identity Provider. This is the base interface for all provider types.
type OAuth2ProviderOption ¶ added in v0.7.2
type OAuth2ProviderOption func(*BaseOAuth2Provider)
OAuth2ProviderOption configures a BaseOAuth2Provider.
func WithOAuth2HTTPClient ¶ added in v0.7.2
func WithOAuth2HTTPClient(client *http.Client) OAuth2ProviderOption
WithOAuth2HTTPClient sets a custom HTTP client.
type OIDCConfig ¶ added in v0.9.1
type OIDCConfig struct {
CommonOAuthConfig
// Issuer is the URL of the upstream OIDC provider (e.g., https://accounts.google.com).
// The provider will fetch endpoints from {Issuer}/.well-known/openid-configuration.
Issuer string
}
OIDCConfig contains configuration for OIDC providers that support discovery.
func (*OIDCConfig) Validate ¶ added in v0.9.1
func (c *OIDCConfig) Validate() error
Validate checks that OIDCConfig has all required fields and valid values.
type OIDCProviderImpl ¶ added in v0.9.1
type OIDCProviderImpl struct {
*BaseOAuth2Provider // Embed for shared OAuth 2.0 logic
// contains filtered or unexported fields
}
OIDCProviderImpl implements OAuth2Provider for OIDC-compliant identity providers. It embeds BaseOAuth2Provider to share common OAuth 2.0 logic while adding OIDC-specific functionality like discovery and ID token validation. The ResolveIdentity method is overridden to validate ID tokens per OIDC spec.
func NewOIDCProvider ¶ added in v0.9.1
func NewOIDCProvider( ctx context.Context, config *OIDCConfig, opts ...OIDCProviderOption, ) (*OIDCProviderImpl, error)
NewOIDCProvider creates a new OIDC provider. It performs OIDC discovery to fetch endpoints and then constructs the underlying OAuth2 configuration from the discovered endpoints.
func (*OIDCProviderImpl) ResolveIdentity ¶ added in v0.9.1
func (p *OIDCProviderImpl) ResolveIdentity(ctx context.Context, tokens *Tokens, nonce string) (string, error)
ResolveIdentity resolves user identity from tokens by validating the ID token. Per OIDC Core Section 3.1.3.3, ID token MUST be present in successful token responses when the openid scope was requested. Returns an error if no ID token is present - use BaseOAuth2Provider for pure OAuth 2.0 flows without OIDC.
func (*OIDCProviderImpl) Type ¶ added in v0.9.1
func (*OIDCProviderImpl) Type() ProviderType
Type returns the provider type.
type OIDCProviderOption ¶ added in v0.9.1
type OIDCProviderOption func(*OIDCProviderImpl)
OIDCProviderOption configures an OIDCProvider.
func WithForceConsentScreen ¶ added in v0.9.1
func WithForceConsentScreen(force bool) OIDCProviderOption
WithForceConsentScreen configures the provider to always request the consent screen from the identity provider. When enabled, the "prompt=consent" parameter is added to authorization requests, forcing the user to re-consent even if they have previously authorized the application.
This is useful for:
- Testing OAuth flows to verify consent screen behavior
- Obtaining a new refresh token when the original has been lost or revoked
- Ensuring the user explicitly confirms permissions after scope changes
- Applications that require explicit user consent on every authentication
func WithHTTPClient ¶ added in v0.9.1
func WithHTTPClient(client *http.Client) OIDCProviderOption
WithHTTPClient sets a custom HTTP client for the provider.
type ProviderType ¶ added in v0.7.2
type ProviderType string
ProviderType identifies the type of upstream Identity Provider.
const ( // ProviderTypeOAuth2 is for pure OAuth 2.0 providers with explicit endpoints. ProviderTypeOAuth2 ProviderType = "oauth2" )
const ( // ProviderTypeOIDC is for OIDC providers that support discovery. ProviderTypeOIDC ProviderType = "oidc" )
type Tokens ¶
type Tokens struct {
// AccessToken is the access token from the upstream IDP.
AccessToken string
// RefreshToken is the refresh token from the upstream IDP (if provided).
RefreshToken string
// IDToken is the ID token from the upstream IDP (for OIDC).
IDToken string
// ExpiresAt is when the access token expires.
ExpiresAt time.Time
}
Tokens represents the tokens obtained from an upstream Identity Provider. This type is used for token exchange with the IDP, but stored separately (see storage.IDPTokens for the storage representation).
type UserInfo ¶
type UserInfo struct {
// Subject is the unique identifier for the user (sub claim).
Subject string `json:"sub"`
// Email is the user's email address.
Email string `json:"email,omitempty"`
// Name is the user's full name.
Name string `json:"name,omitempty"`
// Claims contains all claims returned by the userinfo endpoint.
Claims map[string]any `json:"-"`
}
UserInfo contains user information retrieved from the upstream IDP.
type UserInfoConfig ¶ added in v0.7.2
type UserInfoConfig struct {
// EndpointURL is the URL of the userinfo endpoint (required).
EndpointURL string `json:"endpoint_url" yaml:"endpoint_url"`
// HTTPMethod is the HTTP method to use (default: GET).
HTTPMethod string `json:"http_method,omitempty" yaml:"http_method,omitempty"`
// AdditionalHeaders contains extra headers to include in the request.
AdditionalHeaders map[string]string `json:"additional_headers,omitempty" yaml:"additional_headers,omitempty"`
// FieldMapping contains custom field mapping configuration.
// If nil, standard OIDC field names are used ("sub", "name", "email").
FieldMapping *UserInfoFieldMapping `json:"field_mapping,omitempty" yaml:"field_mapping,omitempty"`
}
UserInfoConfig contains configuration for fetching user information from an upstream provider. This supports both standard OIDC UserInfo endpoints and custom provider-specific endpoints. Authentication is always performed using Bearer token in the Authorization header.
func (*UserInfoConfig) Validate ¶ added in v0.7.2
func (c *UserInfoConfig) Validate() error
Validate checks that UserInfoConfig has all required fields and valid values.
type UserInfoFieldMapping ¶ added in v0.7.2
type UserInfoFieldMapping struct {
// SubjectFields is an ordered list of field names to try for the user ID.
// The first non-empty value found will be used.
// Default: ["sub"]
SubjectFields []string `json:"subject_fields,omitempty" yaml:"subject_fields,omitempty"`
// NameFields is an ordered list of field names to try for the display name.
// The first non-empty value found will be used.
// Default: ["name"]
NameFields []string `json:"name_fields,omitempty" yaml:"name_fields,omitempty"`
// EmailFields is an ordered list of field names to try for the email address.
// The first non-empty value found will be used.
// Default: ["email"]
EmailFields []string `json:"email_fields,omitempty" yaml:"email_fields,omitempty"`
}
UserInfoFieldMapping maps provider-specific field names to standard UserInfo fields. This allows adapting non-standard provider responses to the canonical UserInfo structure.
Each field supports an ordered list of claim names to try. The first non-empty value found will be used. This enables compatibility with providers that use non-standard claim names (e.g., GitHub uses "id" instead of "sub", "login" instead of "preferred_username").
Example for GitHub:
mapping := &UserInfoFieldMapping{
SubjectFields: []string{"id", "login"},
NameFields: []string{"name", "login"},
EmailFields: []string{"email"},
}
func (*UserInfoFieldMapping) GetEmailFields ¶ added in v0.8.3
func (m *UserInfoFieldMapping) GetEmailFields() []string
GetEmailFields returns the configured email fields or the default.
func (*UserInfoFieldMapping) GetNameFields ¶ added in v0.8.3
func (m *UserInfoFieldMapping) GetNameFields() []string
GetNameFields returns the configured name fields or the default.
func (*UserInfoFieldMapping) GetSubjectFields ¶ added in v0.8.3
func (m *UserInfoFieldMapping) GetSubjectFields() []string
GetSubjectFields returns the configured subject fields or the default.
func (*UserInfoFieldMapping) ResolveEmail ¶ added in v0.8.3
func (m *UserInfoFieldMapping) ResolveEmail(claims map[string]any) string
ResolveEmail extracts the email from claims using the configured mapping. Returns an empty string if no email is found (email is optional).
func (*UserInfoFieldMapping) ResolveName ¶ added in v0.8.3
func (m *UserInfoFieldMapping) ResolveName(claims map[string]any) string
ResolveName extracts the display name from claims using the configured mapping. Returns an empty string if no name is found (name is optional).
func (*UserInfoFieldMapping) ResolveSubject ¶ added in v0.8.0
func (m *UserInfoFieldMapping) ResolveSubject(claims map[string]any) (string, error)
ResolveSubject extracts the subject (user ID) from claims using the configured mapping. Returns an error if no subject can be resolved (subject is required).