oauth

package
v1.3.0 Latest Latest
Warning

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

Go to latest
Published: Oct 23, 2026 License: MIT, MIT Imports: 19 Imported by: 0

README

GoModularAuth OAuth Plugin (gomodularauth-oauth)

Generic, provider-agnostic OAuth 2.0 and OpenID Connect (OIDC) client plugin for GoModularAuth. Provides enterprise-grade authentication via external identity providers (Google, GitHub, GitLab, Discord, Microsoft, Keycloak, Auth0, etc.) with strict adherence to RFC 6749, RFC 7636 (PKCE), cryptographically validated state anti-replay protection, safe account linking policies, and native integration with GoModularAuth Core.

Installation

go get github.com/BladiCreator/go-modular-auth/plugins/oauth

Quick Start


Detailed guide: doc/plugins/oauth.md.

Documentation

Overview

Package oauth implements OAuth 2.0 client authentication and social account linking for Google, GitHub, and custom providers with PKCE support.

Index

Constants

View Source
const (
	// EventOAuthStateCreated is emitted when a new OAuth state session is initiated.
	EventOAuthStateCreated auth.EventName = "oauth.state_created"

	// EventOAuthAuthenticated is emitted when a user successfully authenticates via OAuth.
	EventOAuthAuthenticated auth.EventName = "oauth.authenticated"

	// EventOAuthUserCreated is emitted when a new Core User is registered during OAuth flow.
	EventOAuthUserCreated auth.EventName = "oauth.user_created"

	// EventOAuthAccountLinked is emitted when a provider account is linked to a Core User.
	EventOAuthAccountLinked auth.EventName = "oauth.account_linked"

	// EventOAuthAccountUnlinked is emitted when a provider account link is removed.
	EventOAuthAccountUnlinked auth.EventName = "oauth.account_unlinked"

	// EventOAuthFailed is emitted when an OAuth authentication flow fails.
	EventOAuthFailed auth.EventName = "oauth.failed"
)

OAuth event names published through the core Event dispatcher.

View Source
const (
	// PKCEMethodS256 is the standard SHA-256 PKCE code challenge method.
	PKCEMethodS256 = "S256"

	// PKCEMethodPlain is the plain text PKCE method (RFC 7636 allows it, but S256 is strictly preferred).
	PKCEMethodPlain = "plain"

	// DefaultVerifierByteLength generates 32 bytes of entropy, producing 43 base64url characters.
	DefaultVerifierByteLength = 32
)
View Source
const (
	// DefaultStateTTL is the default validity duration for an in-flight OAuth state session (10 minutes).
	DefaultStateTTL = 10 * time.Minute
)
View Source
const (
	ModelOAuthStates = "oauth_states"
)

Variables

View Source
var (
	// ErrProviderNotFound is returned when attempting to use a provider that was not registered.
	ErrProviderNotFound = errors.New("oauth: provider not found")

	// ErrProviderAlreadyExists is returned when registering a provider with a duplicate name.
	ErrProviderAlreadyExists = errors.New("oauth: provider already registered")

	// ErrInvalidState is returned when an OAuth state string is malformed or invalid.
	ErrInvalidState = errors.New("oauth: invalid state")

	// ErrStateNotFound is returned when the state session does not exist in store.
	ErrStateNotFound = errors.New("oauth: state session not found")

	// ErrStateExpired is returned when the state session has expired.
	ErrStateExpired = errors.New("oauth: state session expired")

	// ErrStateAlreadyConsumed is returned when a state session was already used (anti-replay).
	ErrStateAlreadyConsumed = errors.New("oauth: state session already consumed")

	// ErrInvalidRedirectURI is returned when the supplied redirect URI is not in the provider's allowed list or mismatches the initiated flow.
	ErrInvalidRedirectURI = errors.New("oauth: invalid redirect uri")

	// ErrPKCEMismatch is returned when PKCE verification fails.
	ErrPKCEMismatch = errors.New("oauth: pkce verification failed")

	// ErrTokenExchangeFailed is returned when exchanging the authorization code with the provider fails.
	ErrTokenExchangeFailed = errors.New("oauth: token exchange failed")

	// ErrProfileFetchFailed is returned when fetching the user profile from the provider fails.
	ErrProfileFetchFailed = errors.New("oauth: profile fetch failed")

	// ErrInvalidProfile is returned when the provider profile lacks essential identity claims (e.g. subject / ID).
	ErrInvalidProfile = errors.New("oauth: invalid provider profile")

	// ErrAccountConflict is returned when an existing user with the same email exists but account linking is disabled.
	ErrAccountConflict = errors.New("oauth: account with this email already exists")

	// ErrAccountLinkingRequiresConsent is returned when an OAuth login matches an existing user's email,
	// but automatic linking is prohibited without explicit authentication and linking consent.
	// This prevents unsafe automatic account linking attacks.
	ErrAccountLinkingRequiresConsent = errors.New("oauth: existing user found; explicit account linking required")

	// ErrUnverifiedEmailLinkingProhibited is returned when automatic account linking is requested
	// for an email that is not verified either by the provider or within core.
	ErrUnverifiedEmailLinkingProhibited = errors.New("oauth: cannot link account with unverified email")

	// ErrAccountAlreadyLinked is returned when the provider account is already linked to another user.
	ErrAccountAlreadyLinked = errors.New("oauth: provider account is already linked to a user")

	// ErrCannotUnlinkSoleAuthMethod is returned when unlinking an OAuth provider would leave the user without any credentials or accounts.
	ErrCannotUnlinkSoleAuthMethod = errors.New("oauth: cannot unlink sole authentication method")

	// ErrSignUpDisabled is returned when a new user attempts to sign up via OAuth but automatic sign up is disabled.
	ErrSignUpDisabled = errors.New("oauth: sign up is disabled for new users")

	// ErrInvalidAction is returned when a state session action does not match the attempted completion flow.
	ErrInvalidAction = errors.New("oauth: state action mismatch")

	// ErrInvalidInput is returned when required parameters are missing or invalid.
	ErrInvalidInput = errors.New("oauth: invalid input")

	// ErrAccountNotFound is returned when an account lookup yields no record.
	ErrAccountNotFound = errors.New("oauth: account not found")
)

Functions

func ComputeS256Challenge

func ComputeS256Challenge(verifier string) string

ComputeS256Challenge calculates the BASE64URL-ENCODED SHA-256 challenge from a code verifier.

func RegisterSchema

func RegisterSchema(s *schema.Schema) error

RegisterSchema registers the OAuth transient state session model.

func VerifyPKCE

func VerifyPKCE(verifier, challenge, method string) bool

VerifyPKCE validates that a code verifier matches the code challenge using the specified method.

Types

type AccountLinkedPayload

type AccountLinkedPayload struct {
	UserID     string `json:"user_id"`
	AccountID  string `json:"account_id"`
	Provider   string `json:"provider"`
	ProviderID string `json:"provider_id"`
}

AccountLinkedPayload contains details for account link events.

type AccountUnlinkedPayload

type AccountUnlinkedPayload struct {
	UserID    string `json:"user_id"`
	AccountID string `json:"account_id"`
	Provider  string `json:"provider"`
}

AccountUnlinkedPayload contains details for account unlink events.

type Action

type Action string

Action defines the intent of an in-flight OAuth flow.

const (
	// ActionSignIn indicates an OAuth flow intended for authentication or sign up.
	ActionSignIn Action = "sign_in"

	// ActionLink indicates an OAuth flow intended for linking to an existing authenticated user.
	ActionLink Action = "link"
)

type AuthResult

type AuthResult struct {
	// User is the resolved or newly created Core User.
	User *user.User

	// Account is the Core Account linking the User to this OAuth provider.
	Account *account.Account

	// Session is the newly issued Core Session.
	Session *session.Session

	// Token is the plaintext session token returned strictly once at session creation.
	Token string

	// IsNewUser indicates whether this authentication caused a new user to be registered.
	IsNewUser bool

	// TokenResponse holds the tokens returned by the OAuth provider.
	TokenResponse *provider.TokenResponse

	// Profile holds the normalized profile attributes fetched from the provider.
	Profile *provider.ProviderProfile
}

AuthResult represents the outcome of a successful OAuth authentication flow.

type AuthenticatedPayload

type AuthenticatedPayload struct {
	UserID     string `json:"user_id"`
	AccountID  string `json:"account_id"`
	Provider   string `json:"provider"`
	ProviderID string `json:"provider_id"`
	IsNewUser  bool   `json:"is_new_user"`
}

AuthenticatedPayload contains details for successful authentication.

type BeginAuthInput

type BeginAuthInput struct {
	// Provider is the identifier of the registered OAuth provider (e.g. "google").
	Provider string

	// RedirectURI is the target callback URI to which the provider should redirect.
	RedirectURI string

	// Scopes specifies scopes for this request, overriding provider defaults if provided.
	Scopes []string

	// Prompt controls the IdP prompt behavior (e.g. "consent", "select_account").
	Prompt string

	// AccessType controls whether offline access / refresh tokens are requested ("offline").
	AccessType string

	// Metadata stores custom client state associated with this in-flight flow.
	Metadata map[string]string

	// UsePKCE overrides the default PKCE setting for this request (nil uses plugin default, which is true).
	UsePKCE *bool
}

BeginAuthInput contains parameters for initiating an OAuth authorization code flow.

type BeginAuthResult

type BeginAuthResult struct {
	// AuthURL is the complete provider URL the client browser should navigate to.
	AuthURL string

	// State is the unique, cryptographically random state identifier.
	State string

	// CodeVerifier is the generated PKCE verifier (stored securely in state session).
	CodeVerifier string

	// ExpiresAt specifies when the state session expires.
	ExpiresAt time.Time
}

BeginAuthResult contains the computed authorization URL and state session metadata.

type BeginLinkInput

type BeginLinkInput struct {
	// UserID is the ID of the currently authenticated Core user who is linking an account.
	UserID string

	// Provider is the identifier of the registered OAuth provider.
	Provider string

	// RedirectURI is the callback URI.
	RedirectURI string

	// Scopes specifies requested scopes.
	Scopes []string

	// Prompt controls the IdP prompt behavior.
	Prompt string

	// AccessType controls access type.
	AccessType string

	// Metadata stores custom client state.
	Metadata map[string]string

	// UsePKCE overrides default PKCE behavior.
	UsePKCE *bool
}

BeginLinkInput contains parameters for initiating an account linking ceremony.

type CompleteAuthInput

type CompleteAuthInput struct {
	// Provider is the identifier of the OAuth provider.
	Provider string

	// Code is the authorization code returned by the provider.
	Code string

	// State is the state parameter returned by the provider.
	State string

	// RedirectURI is the redirect URI supplied to the provider callback.
	RedirectURI string

	// Session holds optional metadata (IP address, user agent) recorded when creating the core session.
	Session auth.SessionOptions
}

CompleteAuthInput contains parameters received from the provider's OAuth callback.

type CompleteLinkInput

type CompleteLinkInput struct {
	// Code is the authorization code returned by the provider.
	Code string

	// State is the state parameter returned by the provider.
	State string

	// RedirectURI is the callback redirect URI.
	RedirectURI string
}

CompleteLinkInput contains parameters to finalize an explicit account linking flow.

type Config

type Config struct {
	// Providers maps provider name to its Provider implementation.
	Providers map[string]provider.Provider

	// StateTTL is the validity duration of newly issued OAuth state sessions.
	StateTTL time.Duration

	// LinkingPolicy determines whether and how accounts are linked when email addresses collide.
	// Defaults to LinkingPolicyExplicitOnly (preventing unsafe automatic account linking).
	LinkingPolicy LinkingPolicy

	// AllowSignUp controls whether unknown OAuth users are automatically registered.
	// Defaults to true.
	AllowSignUp bool

	// AllowOrphanUser allows unlinking a provider even if it leaves the user with no remaining auth credentials.
	// Defaults to false.
	AllowOrphanUser bool

	// UsePKCE determines whether PKCE (RFC 7636) is generated and required by default.
	// Defaults to true.
	UsePKCE bool

	// Hooks provides lifecycle interceptors for OAuth operations.
	Hooks *Hooks
}

Config configures the OAuth plugin module.

type FailedPayload

type FailedPayload struct {
	Provider string `json:"provider"`
	Reason   string `json:"reason"`
}

FailedPayload contains details for failed OAuth operations.

type Hooks

type Hooks struct {
	// contains filtered or unexported fields
}

Hooks provides lifecycle interceptors for OAuth authentication flows.

func NewHooks

func NewHooks() *Hooks

NewHooks constructs an empty Hooks registry.

func (*Hooks) AfterOAuthSignIn

func (h *Hooks) AfterOAuthSignIn(fn func(ctx context.Context, res *AuthResult) error)

AfterOAuthSignIn registers an interceptor invoked after session creation on successful sign-in.

func (*Hooks) AfterProfileFetched

func (h *Hooks) AfterProfileFetched(fn func(ctx context.Context, profile *provider.ProviderProfile) error)

AfterProfileFetched registers an interceptor invoked immediately after user profile claims are parsed.

func (h *Hooks) BeforeAccountLink(fn func(ctx context.Context, userID string, profile *provider.ProviderProfile) error)

BeforeAccountLink registers an interceptor invoked before linking a provider account to a user.

func (*Hooks) BeforeOAuthRedirect

func (h *Hooks) BeforeOAuthRedirect(fn func(ctx context.Context, in *BeginAuthInput) error)

BeforeOAuthRedirect registers an interceptor invoked before building the authorization URL.

func (*Hooks) BeforeTokenExchange

func (h *Hooks) BeforeTokenExchange(fn func(ctx context.Context, providerName string, code string) error)

BeforeTokenExchange registers an interceptor invoked before code exchange with the provider.

type LinkResult

type LinkResult struct {
	// User is the authenticated Core User who owns the linked account.
	User *user.User

	// Account is the newly created Core Account link.
	Account *account.Account

	// Profile is the normalized profile claims fetched from the provider.
	Profile *provider.ProviderProfile
}

LinkResult represents the outcome of a successful explicit account linking operation.

type LinkingPolicy

type LinkingPolicy string

LinkingPolicy determines how the OAuth plugin resolves collisions when an external OAuth profile presents an email address that already belongs to an existing user in Core.

const (
	// LinkingPolicyExplicitOnly requires account linking to be initiated explicitly
	// by an authenticated user (via BeginLink/CompleteLink).
	// If an unlinked user with a matching email attempts OAuth sign-in, it fails with
	// ErrAccountLinkingRequiresConsent, preventing unsafe automatic account linking.
	LinkingPolicyExplicitOnly LinkingPolicy = "explicit_only"

	// LinkingPolicyVerifiedEmail permits automatic account linking ONLY IF:
	// 1. The external OAuth provider asserts profile.EmailVerified == true.
	// 2. The existing Core User has user.EmailVerified == true.
	// 3. The email addresses match.
	// If either email is not verified, it fails with ErrUnverifiedEmailLinkingProhibited.
	LinkingPolicyVerifiedEmail LinkingPolicy = "verified_email"

	// LinkingPolicyDisabled completely disallows account linking. If a user with the same
	// email already exists, it fails with ErrAccountConflict.
	LinkingPolicyDisabled LinkingPolicy = "disabled"
)

type Module

type Module struct {
	// contains filtered or unexported fields
}

Module implements auth.PluginModule for GoModularAuth OAuth client authentication using plugin-sdk.

func New

func New(opts ...Option) *Module

New constructs a new OAuth client plugin module.

func (*Module) ID

func (m *Module) ID() string

ID returns the module's unique identifier within GoModularAuth.

func (*Module) Init

func (m *Module) Init(h *auth.Host) error

Init initializes the plugin module on the host and registers its capability.

func (*Module) RegisterSchema

func (m *Module) RegisterSchema(s *schema.Schema) error

RegisterSchema registers oauth models in the compiled schema.

type OAuth

type OAuth interface {
	// BeginAuth initiates an OAuth authorization code flow for sign-in or account registration.
	// It validates the redirect URI, generates state and PKCE verifier, persists state, and returns the authorization URL.
	BeginAuth(ctx context.Context, in BeginAuthInput) (*BeginAuthResult, error)

	// BeginLink initiates an OAuth authorization code flow specifically for linking a provider to an existing authenticated user.
	// The user ID is bound to the state session to prevent CSRF and session confusion.
	BeginLink(ctx context.Context, in BeginLinkInput) (*BeginAuthResult, error)

	// CompleteAuth completes the OAuth flow: atomically consumes state, exchanges authorization code for tokens,
	// fetches provider profile, creates or locates the user and Core Account, and creates an active Core Session.
	CompleteAuth(ctx context.Context, in CompleteAuthInput) (*AuthResult, error)

	// CompleteLink completes an explicit account linking flow for the user bound during BeginLink.
	CompleteLink(ctx context.Context, in CompleteLinkInput) (*LinkResult, error)

	// UnlinkAccount removes the link between a user and an OAuth provider.
	// Fails with ErrCannotUnlinkSoleAuthMethod if this is the user's sole authentication method and orphaning is disabled.
	UnlinkAccount(ctx context.Context, userID, provider string) error

	// ListAccounts returns all external OAuth accounts linked to the user.
	ListAccounts(ctx context.Context, userID string) ([]*account.Account, error)

	// GetProvider returns a registered provider by name.
	GetProvider(name string) (provider.Provider, error)

	// PurgeExpired cleans up expired states from the store.
	PurgeExpired(ctx context.Context) (int, error)
}

OAuth is the strongly typed capability provided by the OAuth plugin. Applications and transport adapters retrieve this capability via:

oauthAuth, err := auth.Get[plugins.OAuth](engine)
// or
oauthAuth := auth.MustGet[plugins.OAuth](engine)

type Option

type Option func(*Config)

Option customizes a Config struct.

func WithAllowOrphanUser

func WithAllowOrphanUser(allow bool) Option

WithAllowOrphanUser allows unlinking an OAuth account even if no other auth credentials remain.

func WithAllowSignUp

func WithAllowSignUp(allow bool) Option

WithAllowSignUp specifies whether new user registration via OAuth is allowed.

func WithDefaultPKCE

func WithDefaultPKCE(usePKCE bool) Option

WithDefaultPKCE configures whether PKCE is enabled by default.

func WithHooks

func WithHooks(h *Hooks) Option

WithHooks sets the lifecycle hooks.

func WithLinkingPolicy

func WithLinkingPolicy(policy LinkingPolicy) Option

WithLinkingPolicy configures the policy for resolving account linking collisions.

func WithProvider

func WithProvider(p provider.Provider) Option

WithProvider registers a single OAuth identity provider.

func WithProviders

func WithProviders(providers ...provider.Provider) Option

WithProviders registers multiple OAuth identity providers.

func WithStateTTL

func WithStateTTL(d time.Duration) Option

WithStateTTL configures the lifetime of in-flight state sessions.

type PKCEPair

type PKCEPair struct {
	Verifier  string `json:"verifier"`
	Challenge string `json:"challenge"`
	Method    string `json:"method"`
}

PKCEPair holds the generated code verifier, challenge, and method according to RFC 7636.

func GeneratePKCE

func GeneratePKCE() (*PKCEPair, error)

GeneratePKCE creates a cryptographically secure PKCE code verifier and S256 code challenge.

type StateCreatedPayload

type StateCreatedPayload struct {
	Provider  string    `json:"provider"`
	State     string    `json:"state"`
	Action    string    `json:"action"`
	UserID    string    `json:"user_id,omitempty"`
	ExpiresAt time.Time `json:"expires_at"`
}

StateCreatedPayload contains non-sensitive audit details for state creation.

type StateSession

type StateSession struct {
	State        string            `json:"state"`
	Provider     string            `json:"provider"`
	RedirectURI  string            `json:"redirect_uri"`
	CodeVerifier string            `json:"code_verifier,omitempty"`
	Action       Action            `json:"action"`
	UserID       string            `json:"user_id,omitempty"`
	Metadata     map[string]string `json:"metadata,omitempty"`
	ExpiresAt    time.Time         `json:"expires_at"`
	ConsumedAt   *time.Time        `json:"consumed_at,omitempty"`
	CreatedAt    time.Time         `json:"created_at"`
}

StateSession represents an in-flight, transient OAuth state session with PKCE binding.

func (*StateSession) Consumed

func (s *StateSession) Consumed() bool

Consumed reports whether the state session has already been redeemed.

func (*StateSession) Expired

func (s *StateSession) Expired(now time.Time) bool

Expired reports whether the state session has expired relative to the given time.

func (*StateSession) Valid

func (s *StateSession) Valid(now time.Time) bool

Valid reports whether the state is neither expired nor consumed.

type UserCreatedPayload

type UserCreatedPayload struct {
	UserID     string `json:"user_id"`
	Email      string `json:"email"`
	Provider   string `json:"provider"`
	ProviderID string `json:"provider_id"`
}

UserCreatedPayload contains details for new user creation via OAuth.

Directories

Path Synopsis
Package plugins exposes strongly-typed capability aliases for GoModularAuth plugins.
Package plugins exposes strongly-typed capability aliases for GoModularAuth plugins.
Package provider defines the contracts and models for OAuth 2.0 / OIDC identity providers.
Package provider defines the contracts and models for OAuth 2.0 / OIDC identity providers.

Jump to

Keyboard shortcuts

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