genericoauth

package
v0.27.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 31, 2026 License: MIT Imports: 16 Imported by: 0

Documentation

Index

Constants

View Source
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.

View Source
const PluginID = "generic-oauth"

PluginID is the unique string identifier for the Generic OAuth plugin ("generic-oauth").

Variables

View Source
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

func GeneratePKCE() (verifier string, challenge string, err error)

GeneratePKCE creates a cryptographically secure random code_verifier and its S256 code_challenge.

func GenerateState

func GenerateState() (string, error)

GenerateState generates a random 32-byte hex state token.

func ResolveProviderConfig

func ResolveProviderConfig(ctx context.Context, client *http.Client, cfg *ProviderConfig) error

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

type ExchangeRequest struct {
	Code         string
	RedirectURI  string
	CodeVerifier string
	DeviceID     string
}

ExchangeRequest encapsulates arguments needed to exchange an authorization code for tokens.

type LinkAccountRequestPayload

type LinkAccountRequestPayload struct {
	UserID       string `json:"userId"`
	ProviderID   string `json:"providerId"`
	Code         string `json:"code"`
	CodeVerifier string `json:"codeVerifier,omitempty"`
}

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.

func FetchDiscovery

func FetchDiscovery(ctx context.Context, client *http.Client, discoveryURL string, headers http.Header) (*OIDCDiscoveryDocument, error)

FetchDiscovery retrieves and parses the OpenID Connect discovery document from the specified discovery URL.

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

func WithHTTPClient(client *http.Client) Option

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

func WithStateTTL(ttl time.Duration) Option

WithStateTTL sets the maximum lifetime for OAuth state and PKCE verifier tokens.

type ParamBuilderFunc

type ParamBuilderFunc func(ctx context.Context) map[string]string

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) Config

func (p *Plugin) Config() Config

Config returns the active configuration of the Generic OAuth plugin.

func (*Plugin) GetProvider

func (p *Plugin) GetProvider(providerID string) (*ProviderConfig, error)

GetProvider retrieves a ProviderConfig by provider ID.

func (*Plugin) ID

func (p *Plugin) ID() string

ID returns the unique identifier for the Generic OAuth plugin ("generic-oauth").

func (*Plugin) Init

func (p *Plugin) Init(ctx *plugin.Context) error

Init initializes the plugin within the global GoModularAuth context.

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.

func (*Plugin) SignIn

func (p *Plugin) SignIn(ctx context.Context, providerID string, callbackURL string) (*SignInData, error)

SignIn initiates an OAuth authorization flow for the given provider.

type ProfileMapperFunc

type ProfileMapperFunc func(ctx context.Context, profile map[string]any) (*UserPartial, error)

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 SignInRequestPayload struct {
	ProviderID  string `json:"providerId"`
	CallbackURL string `json:"callbackUrl,omitempty"`
}

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

func DecodeIDToken(idToken string) (*UserInfo, error)

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

type UserInfoFetcherFunc func(ctx context.Context, tokens *Tokens) (*UserInfo, error)

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.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL