Documentation
¶
Index ¶
- Constants
- Variables
- func BuildAuthorizationURL(provider *ProviderConfig, state string, codeChallenge string) (string, error)
- func GeneratePKCE() (verifier string, challenge string, err error)
- func GenerateState() (string, error)
- func ResolveProviderConfig(ctx context.Context, client *http.Client, cfg *ProviderConfig) error
- type AuthMethod
- type Config
- type CookieConfig
- type ExchangeRequest
- type LinkAccountRequestPayload
- type OIDCDiscoveryDocument
- type Option
- type ParamBuilderFunc
- type Plugin
- func (p *Plugin) Callback(ctx context.Context, providerID string, code string, state string, ...) (*entity.User, *entity.Session, *Tokens, error)
- func (p *Plugin) Config() Config
- func (p *Plugin) GetProvider(providerID string) (*ProviderConfig, error)
- func (p *Plugin) ID() string
- func (p *Plugin) Init(ctx *plugin.Context) error
- func (p *Plugin) LinkAccount(ctx context.Context, userID string, providerID string, code string, ...) (*entity.Account, error)
- func (p *Plugin) ServeCallback(w http.ResponseWriter, r *http.Request)
- func (p *Plugin) ServeLinkAccount(w http.ResponseWriter, r *http.Request)
- func (p *Plugin) ServeSignIn(w http.ResponseWriter, r *http.Request)
- func (p *Plugin) SignIn(ctx context.Context, providerID string, callbackURL string) (*SignInData, error)
- type ProfileMapperFunc
- type ProviderConfig
- type Repository
- type ResponseMode
- type SignInData
- type SignInRequestPayload
- type StateData
- type TokenFetcherFunc
- type Tokens
- type UserInfo
- type UserInfoFetcherFunc
- type UserPartial
Constants ¶
const ( // EventOAuthSignInStart is dispatched when an OAuth sign-in / authorization flow starts. EventOAuthSignInStart = "genericoauth:sign_in_start" // EventOAuthSignInSuccess is dispatched after a successful OAuth authentication and session creation. EventOAuthSignInSuccess = "genericoauth:sign_in_success" // EventOAuthSignInFailure is dispatched when an OAuth sign-in flow encounters an error. EventOAuthSignInFailure = "genericoauth:sign_in_failure" // EventOAuthAccountLinked is dispatched when a social account is bound to an existing user. EventOAuthAccountLinked = "genericoauth:account_linked" )
Event topics for the Generic OAuth plugin.
const PluginID = "generic-oauth"
PluginID is the unique string identifier for the Generic OAuth plugin ("generic-oauth").
Variables ¶
var ( // ErrProviderNotFound is returned when a requested provider ID is not registered in plugin configuration. ErrProviderNotFound = errors.New("genericoauth: provider not found") // ErrInvalidState is returned when the OAuth state parameter is missing, invalid, or expired. ErrInvalidState = errors.New("genericoauth: invalid or expired OAuth state") // ErrIssuerMismatch is returned when the discovery metadata issuer does not match the configured issuer. ErrIssuerMismatch = errors.New("genericoauth: discovery issuer mismatch") // ErrInvalidCodeVerifier is returned when PKCE validation fails due to missing or invalid code_verifier. ErrInvalidCodeVerifier = errors.New("genericoauth: missing or invalid PKCE code_verifier") // ErrSignUpDisabled is returned when attempting to sign up a new user when implicit sign-up is disabled. ErrSignUpDisabled = errors.New("genericoauth: new user sign-up is disabled for this provider") // ErrAccountAlreadyLinked is returned when a social profile is already bound to another user account. ErrAccountAlreadyLinked = errors.New("genericoauth: social account already linked to another user") // ErrUserInfoFailed is returned when user info could not be fetched from the provider. ErrUserInfoFailed = errors.New("genericoauth: failed to retrieve user info from provider") // ErrUserNotFound is returned when looking up a user that does not exist. ErrUserNotFound = errors.New("genericoauth: user not found") // ErrInvalidParameter is returned when required parameters are missing or malformed. ErrInvalidParameter = errors.New("genericoauth: invalid or missing parameter") // ErrCodeExchangeFailed is returned when exchanging the authorization code for tokens fails. ErrCodeExchangeFailed = errors.New("genericoauth: failed to exchange authorization code for tokens") )
Sentinel errors for the Generic OAuth plugin.
Functions ¶
func BuildAuthorizationURL ¶
func BuildAuthorizationURL(provider *ProviderConfig, state string, codeChallenge string) (string, error)
BuildAuthorizationURL constructs the authorization URL for initiating OAuth2 login flow.
func GeneratePKCE ¶
GeneratePKCE creates a cryptographically secure random code_verifier and its S256 code_challenge.
func GenerateState ¶
GenerateState generates a random 32-byte hex state token.
func ResolveProviderConfig ¶
ResolveProviderConfig populates missing AuthorizationURL, TokenURL, UserInfoURL, and Issuer from discovery if discoveryURL is provided.
Types ¶
type AuthMethod ¶
type AuthMethod string
AuthMethod defines the client authentication method used at the token endpoint.
const ( AuthMethodPost AuthMethod = "post" AuthMethodBasic AuthMethod = "basic" )
type Config ¶
type Config struct {
Providers map[string]*ProviderConfig
HTTPClient *http.Client
CookieConfig CookieConfig
StateTTL time.Duration
}
Config defines the overall Generic OAuth plugin configuration.
func DefaultConfig ¶
func DefaultConfig() Config
DefaultConfig returns reasonable default configuration values.
type CookieConfig ¶
type CookieConfig struct {
Name string
Secret string
Domain string
Path string
Secure bool
HTTPOnly bool
SameSite http.SameSite
MaxAge time.Duration
}
CookieConfig configures HTTP state and PKCE cookies.
type ExchangeRequest ¶
ExchangeRequest encapsulates arguments needed to exchange an authorization code for tokens.
type OIDCDiscoveryDocument ¶
type OIDCDiscoveryDocument struct {
Issuer string `json:"issuer"`
AuthorizationEndpoint string `json:"authorization_endpoint"`
TokenEndpoint string `json:"token_endpoint"`
UserInfoEndpoint string `json:"userinfo_endpoint"`
JWKSURI string `json:"jwks_uri"`
ScopesSupported []string `json:"scopes_supported,omitempty"`
}
OIDCDiscoveryDocument represents the metadata returned by .well-known/openid-configuration.
type Option ¶
type Option func(*Config)
Option defines a functional option for configuring the Generic OAuth plugin.
func WithCookieConfig ¶
func WithCookieConfig(cfg CookieConfig) Option
WithCookieConfig sets custom cookie options for state and PKCE tracking.
func WithHTTPClient ¶
WithHTTPClient overrides the default HTTP client used for discovery, token exchange, and user info calls.
func WithProvider ¶
func WithProvider(cfg *ProviderConfig) Option
WithProvider registers a provider configuration in the plugin.
func WithStateTTL ¶
WithStateTTL sets the maximum lifetime for OAuth state and PKCE verifier tokens.
type ParamBuilderFunc ¶
Custom hook function signatures.
type Plugin ¶
type Plugin struct {
// contains filtered or unexported fields
}
Plugin implements Generic OAuth authentication for go-modular-auth.
func New ¶
func New(repo Repository, opts ...Option) *Plugin
New creates a new Generic OAuth plugin instance configured with a repository and options.
func (*Plugin) Callback ¶
func (p *Plugin) Callback(ctx context.Context, providerID string, code string, state string, codeVerifier string) (*entity.User, *entity.Session, *Tokens, error)
Callback handles authorization code exchange, user lookup/creation, account linking, and session initialization.
func (*Plugin) GetProvider ¶
func (p *Plugin) GetProvider(providerID string) (*ProviderConfig, error)
GetProvider retrieves a ProviderConfig by provider ID.
func (*Plugin) ID ¶
ID returns the unique identifier for the Generic OAuth plugin ("generic-oauth").
func (*Plugin) LinkAccount ¶
func (p *Plugin) LinkAccount(ctx context.Context, userID string, providerID string, code string, codeVerifier string) (*entity.Account, error)
LinkAccount explicitly links a social profile to an already authenticated user.
func (*Plugin) ServeCallback ¶
func (p *Plugin) ServeCallback(w http.ResponseWriter, r *http.Request)
ServeCallback handles OAuth2 redirect callbacks.
func (*Plugin) ServeLinkAccount ¶
func (p *Plugin) ServeLinkAccount(w http.ResponseWriter, r *http.Request)
ServeLinkAccount handles explicit social account linking to an active user account.
func (*Plugin) ServeSignIn ¶
func (p *Plugin) ServeSignIn(w http.ResponseWriter, r *http.Request)
ServeSignIn handles HTTP requests to initiate an OAuth2 flow.
type ProfileMapperFunc ¶
Custom hook function signatures.
type ProviderConfig ¶
type ProviderConfig struct {
ProviderID string `json:"provider_id"`
DiscoveryURL string `json:"discovery_url,omitempty"`
Issuer string `json:"issuer,omitempty"`
RequireIssuerValidation bool `json:"require_issuer_validation,omitempty"`
AuthorizationURL string `json:"authorization_url,omitempty"`
TokenURL string `json:"token_url,omitempty"`
UserInfoURL string `json:"user_info_url,omitempty"`
ClientID string `json:"client_id"`
ClientSecret string `json:"client_secret,omitempty"`
Scopes []string `json:"scopes,omitempty"`
RedirectURI string `json:"redirect_uri,omitempty"`
ResponseType string `json:"response_type,omitempty"`
ResponseMode ResponseMode `json:"response_mode,omitempty"`
Prompt string `json:"prompt,omitempty"`
PKCE bool `json:"pkce,omitempty"`
AccessType string `json:"access_type,omitempty"`
AccessTokenExpiresIn time.Duration `json:"access_token_expires_in,omitempty"`
Authentication AuthMethod `json:"authentication,omitempty"`
DisableImplicitSignUp bool `json:"disable_implicit_sign_up,omitempty"`
DisableSignUp bool `json:"disable_sign_up,omitempty"`
OverrideUserInfo bool `json:"override_user_info,omitempty"`
DiscoveryHeaders http.Header `json:"-"`
AuthorizationHeaders http.Header `json:"-"`
AuthURLParams map[string]string `json:"auth_url_params,omitempty"`
TokenURLParams map[string]string `json:"token_url_params,omitempty"`
// Custom Hooks
GetToken TokenFetcherFunc `json:"-"`
GetUserInfo UserInfoFetcherFunc `json:"-"`
MapProfileToUser ProfileMapperFunc `json:"-"`
}
ProviderConfig specifies complete setup parameters for a generic OAuth2/OIDC provider.
type Repository ¶
type Repository interface {
// GetUserByEmail finds a user entity matching the provided email address.
//
// Function:
// Used during OAuth Callback to match an existing user account by email when implicit account linking is enabled.
//
// Storage:
// Database (GORM / SQL) - Relational user entity lookup.
//
// Arguments:
// - ctx: Request cancellation context.
// - email: Normalized email address string.
//
// Returns:
// - *entity.User: Matching user profile if found.
// - error: ErrUserNotFound if missing, or database error.
//
// Example SQL:
// SELECT id, email, name, email_verified, created_at, updated_at FROM users WHERE email = $1 LIMIT 1;
GetUserByEmail(ctx context.Context, email string) (*entity.User, error)
// GetUserByID finds a user entity by unique primary key identifier.
//
// Function:
// Used during LinkAccount flow to verify existence of the target user account.
//
// Storage:
// Database (GORM / SQL) - User primary key lookup.
//
// Arguments:
// - ctx: Request cancellation context.
// - id: Unique primary key user ID.
//
// Returns:
// - *entity.User: User profile entity.
// - error: ErrUserNotFound if missing.
//
// Example SQL:
// SELECT id, email, name, email_verified, created_at, updated_at FROM users WHERE id = $1 LIMIT 1;
GetUserByID(ctx context.Context, id string) (*entity.User, error)
// GetAccountByProvider finds a social account binding by provider ID and provider subject/account ID.
//
// Function:
// Called during OAuth Callback to check if this social account was previously linked to a user.
//
// Storage:
// Database (GORM / SQL) - Social provider binding query.
//
// Arguments:
// - ctx: Request cancellation context.
// - providerID: Registered provider key (e.g. "github", "google", "auth0").
// - accountID: Provider sub/id returned in UserInfo response.
//
// Returns:
// - *entity.Account: Linked account entity if found, or nil if not linked yet.
// - error: Nil if not found, or database error.
//
// Example SQL:
// SELECT id, user_id, provider, account_id, created_at, updated_at FROM accounts WHERE provider = $1 AND account_id = $2 LIMIT 1;
GetAccountByProvider(ctx context.Context, providerID, accountID string) (*entity.Account, error)
// CreateUser persists a new user entity in storage.
//
// Function:
// Called during OAuth Callback when a new social user logs in and auto-signup is enabled.
//
// Storage:
// Database (GORM / SQL) - User creation.
//
// Arguments:
// - ctx: Request cancellation context.
// - user: User entity to persist.
//
// Returns:
// - *entity.User: Newly persisted user entity.
// - error: Nil on success, or database error.
//
// Example SQL:
// INSERT INTO users (id, email, name, email_verified, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6);
CreateUser(ctx context.Context, user *entity.User) (*entity.User, error)
// CreateAccount persists a new social account binding linking a provider subject ID to a user ID.
//
// Function:
// Called after user creation or account linking to store provider credentials/tokens.
//
// Storage:
// Database (GORM / SQL) - Account entity creation.
//
// Arguments:
// - ctx: Request cancellation context.
// - account: Account entity containing UserID, Provider, and AccountID.
//
// Returns:
// - *entity.Account: Persisted account entity.
// - error: ErrAccountAlreadyLinked if bound to another user.
//
// Example SQL:
// INSERT INTO accounts (id, user_id, provider, account_id, access_token, refresh_token, created_at, updated_at)
// VALUES ($1, $2, $3, $4, $5, $6, $7, $8);
CreateAccount(ctx context.Context, account *entity.Account) (*entity.Account, error)
// CreateSession persists a new active user session after successful OAuth authentication.
//
// Function:
// Called at the end of Callback flow to issue a new session.
//
// Storage:
// Database (GORM / SQL) - Active session creation.
//
// Arguments:
// - ctx: Request cancellation context.
// - session: Active session entity to persist.
//
// Returns:
// - *entity.Session: Active session entity.
// - error: Nil on success, or database error.
//
// Example SQL:
// INSERT INTO sessions (id, user_id, token, expires_at, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6);
CreateSession(ctx context.Context, session *entity.Session) (*entity.Session, error)
// SaveState persists transient state metadata (e.g. for non-cookie state storage).
//
// Function:
// Called during SignIn to store PKCE code_verifier and redirect URLs when cookie state is not used.
//
// Storage:
// Cache (Redis / In-Memory TTL) - Transient OAuth state with expiration TTL.
//
// Arguments:
// - ctx: Request cancellation context.
// - key: State key identifier.
// - data: StateData struct.
// - ttl: Expiration lifetime duration.
//
// Returns:
// - error: Nil on success.
//
// Example Cache (Redis):
// err := rdb.Set(ctx, "oauth:state:" + key, bytes, ttl).Err()
SaveState(ctx context.Context, key string, data *StateData, ttl time.Duration) error
// GetState retrieves transient state metadata by state key.
//
// Function:
// Called during Callback to restore PKCE code_verifier and callback URL.
//
// Storage:
// Cache (Redis / In-Memory TTL) - Transient state lookup.
//
// Arguments:
// - ctx: Request cancellation context.
// - key: State key identifier.
//
// Returns:
// - *StateData: State metadata if found and not expired.
// - error: ErrInvalidState if missing or expired.
//
// Example Cache (Redis):
// val, err := rdb.Get(ctx, "oauth:state:" + key).Bytes()
GetState(ctx context.Context, key string) (*StateData, error)
// DeleteState removes transient state metadata.
//
// Function:
// Called after consuming state during Callback to prevent state replay.
//
// Storage:
// Cache (Redis / In-Memory TTL) - Key eviction from Redis/memory.
//
// Arguments:
// - ctx: Request cancellation context.
// - key: State key identifier.
//
// Returns:
// - error: Nil on success.
//
// Example Cache (Redis):
// err := rdb.Del(ctx, "oauth:state:" + key).Err()
DeleteState(ctx context.Context, key string) error
}
Repository defines storage operations required by the Generic OAuth plugin. Implement this interface on your custom database adapter (e.g. PostgreSQL, MySQL, SQLite, MongoDB, GORM).
Implementation Example (GORM / database/sql): ¶
type GormGenericOAuthRepository struct {
db *gorm.DB
}
func (r *GormGenericOAuthRepository) GetAccountByProvider(ctx context.Context, providerID, accountID string) (*entity.Account, error) {
var acc entity.Account
if err := r.db.WithContext(ctx).Where("provider = ? AND account_id = ?", providerID, accountID).First(&acc).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, err
}
return &acc, nil
}
Storage and Caching Recommendation (Transient OAuth State Storage): ¶
Transient OAuth state parameters (`SaveState`, `GetState`, `DeleteState`) holding PKCE code_verifiers and callback URLs are short-lived. Storing transient state in Redis key-value storage with TTL provides optimal performance:
type RedisGenericOAuthStateStore struct {
redis *redis.Client
}
func (r *RedisGenericOAuthStateStore) SaveState(ctx context.Context, key string, data *genericoauth.StateData, ttl time.Duration) error {
bytes, _ := json.Marshal(data)
return r.redis.Set(ctx, "oauth:state:"+key, bytes, ttl).Err()
}
func (r *RedisGenericOAuthStateStore) GetState(ctx context.Context, key string) (*genericoauth.StateData, error) {
val, err := r.redis.Get(ctx, "oauth:state:"+key).Bytes()
if err != nil {
return nil, genericoauth.ErrInvalidState
}
var state genericoauth.StateData
_ = json.Unmarshal(val, &state)
return &state, nil
}
type ResponseMode ¶
type ResponseMode string
ResponseMode defines the authorization response mode (query or form_post).
const ( ResponseModeQuery ResponseMode = "query" ResponseModeFormPost ResponseMode = "form_post" )
type SignInData ¶
type SignInData struct {
URL string `json:"url"`
State string `json:"state"`
CodeVerifier string `json:"code_verifier,omitempty"`
Redirect bool `json:"redirect"`
}
SignInData contains authorization metadata produced when initiating sign-in.
type SignInRequestPayload ¶
type StateData ¶
type StateData struct {
ProviderID string `json:"provider_id"`
CodeVerifier string `json:"code_verifier,omitempty"`
CallbackURL string `json:"callback_url,omitempty"`
CreatedAt time.Time `json:"created_at"`
}
StateData represents ephemeral authorization state metadata.
type TokenFetcherFunc ¶
type TokenFetcherFunc func(ctx context.Context, req ExchangeRequest) (*Tokens, error)
Custom hook function signatures.
type Tokens ¶
type Tokens struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token,omitempty"`
IDToken string `json:"id_token,omitempty"`
TokenType string `json:"token_type,omitempty"`
AccessTokenExpiresAt time.Time `json:"access_token_expires_at,omitempty"`
RefreshTokenExpiresAt time.Time `json:"refresh_token_expires_at,omitempty"`
Scopes []string `json:"scopes,omitempty"`
Raw map[string]any `json:"raw,omitempty"`
}
Tokens holds access, refresh, and ID tokens returned by an OAuth2/OIDC provider.
func ExchangeCode ¶
func ExchangeCode(ctx context.Context, client *http.Client, provider *ProviderConfig, req ExchangeRequest) (*Tokens, error)
ExchangeCode performs standard OAuth 2.0 authorization code exchange for tokens.
type UserInfo ¶
type UserInfo struct {
ID string `json:"id,omitempty"`
Sub string `json:"sub,omitempty"`
Email string `json:"email,omitempty"`
EmailVerified bool `json:"email_verified,omitempty"`
Name string `json:"name,omitempty"`
Picture string `json:"picture,omitempty"`
Raw map[string]any `json:"raw,omitempty"`
}
UserInfo represents normalized profile data obtained from the provider.
func DecodeIDToken ¶
DecodeIDToken decodifies the JWT payload in id_token without verifying signature (for claims extraction).
func FetchUserInfo ¶
func FetchUserInfo(ctx context.Context, client *http.Client, provider *ProviderConfig, tokens *Tokens) (*UserInfo, error)
FetchUserInfo obtains profile information from provider user_info_url or decodes id_token.
type UserInfoFetcherFunc ¶
Custom hook function signatures.
type UserPartial ¶
type UserPartial struct {
ID string `json:"id,omitempty"`
Email string `json:"email,omitempty"`
EmailVerified bool `json:"email_verified,omitempty"`
Name string `json:"name,omitempty"`
Image string `json:"image,omitempty"`
CustomFields map[string]any `json:"custom_fields,omitempty"`
}
UserPartial represents mapped fields ready for user creation or updating.