oauth

package
v1.7.5 Latest Latest
Warning

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

Go to latest
Published: Jun 30, 2026 License: AGPL-3.0 Imports: 19 Imported by: 0

Documentation

Overview

Package oauth implements provider-side OAuth code-exchange flows for authenticating end users into the identity service.

Identity does OAuth FOR LOGIN ONLY: it accepts an authorization code from the frontend, swaps it with the provider for an ID token (or userinfo response), verifies the user's identity, and then mints OUR own JWT. Provider access/refresh tokens are NEVER stored — that's a separate "connections" service concern.

The Exchanger interface decouples the auth flow from any specific provider; a Registry holds the per-provider implementations the service layer dispatches to via the "provider" string in the RPC.

Currently supported providers:

  • "google": OIDC. ID token verified via JWKS (RS256).
  • "microsoft": OIDC via Azure AD common endpoint. ID token verified via JWKS; per-tenant issuer accepted.
  • "github": NOT OIDC. We exchange the code, then call /user and /user/emails to discover the canonical (verified, primary) email.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrCodeExchangeFailed indicates the provider rejected our
	// token-endpoint POST or returned an unparseable response.
	ErrCodeExchangeFailed = errors.New("oauth: code exchange failed")

	// ErrIdentityVerification indicates the ID token signature, issuer,
	// audience, or expiry could not be validated.
	ErrIdentityVerification = errors.New("oauth: identity verification failed")

	// ErrEmailNotVerified indicates the provider returned an unverified
	// email; we refuse to log such users in.
	ErrEmailNotVerified = errors.New("oauth: provider reported email is not verified")

	// ErrStateValidation indicates the OAuth callback state could not be
	// validated against the server-minted state token.
	ErrStateValidation = errors.New("oauth: state validation failed")
)

Common error sentinels. Callers (e.g. the service layer) can errors.Is against these to map to RPC error codes.

Functions

func CodeChallengeS256

func CodeChallengeS256(verifier string) string

CodeChallengeS256 returns the PKCE S256 code challenge for verifier.

func GenerateCodeVerifier

func GenerateCodeVerifier() (string, error)

GenerateCodeVerifier returns a PKCE code verifier.

func GenerateState

func GenerateState() (string, error)

GenerateState returns a high-entropy OAuth state string.

func IssueHostedStateToken added in v0.9.0

func IssueHostedStateToken(
	ctx context.Context,
	signer identityjwt.Signer,
	provider, redirectURI, returnTo, state, codeVerifier, csrfToken string,
	expiry time.Duration,
	now time.Time,
) (string, error)

IssueHostedStateToken signs a hosted-flow state token. redirectURI is the identity-owned callback URL registered with the provider; returnTo is the validated app URL the callback redirects to.

func IssueStateToken

func IssueStateToken(
	ctx context.Context,
	signer identityjwt.Signer,
	provider, redirectURI, state, codeVerifier string,
	expiry time.Duration,
	now time.Time,
) (string, error)

Types

type AppleConfig added in v1.5.0

type AppleConfig struct {
	ClientID   string
	TeamID     string
	KeyID      string
	PrivateKey string

	TokenURL string
	JWKSURL  string
	Issuer   string

	HTTPClient   *http.Client
	JWKSCacheTTL time.Duration
	Now          func() time.Time
}

AppleConfig configures an Apple Exchanger. ClientID, TeamID, KeyID, and PrivateKey are required.

type Authorizer

type Authorizer interface {
	AuthorizationURL(ctx context.Context, redirectURI, state, codeChallenge string) (string, error)
}

Authorizer builds the provider authorization URL for the first half of the OAuth authorization-code flow.

type ExchangeParams added in v1.5.0

type ExchangeParams struct {
	Code             string
	RedirectURI      string
	CodeVerifier     string // Optional PKCE code_verifier
	AppleUserPayload string // Optional form-post payload for Apple first-time login
}

ExchangeParams contains the arguments for the OAuth token exchange.

type Exchanger

type Exchanger interface {
	Exchange(ctx context.Context, params ExchangeParams) (*Identity, error)
}

Exchanger swaps an OAuth authorization code for a verified user identity. Implementations are responsible for:

  1. POSTing to the provider's token endpoint with client_id / client_secret / code / redirect_uri.
  2. Verifying the resulting ID token (OIDC providers) OR fetching userinfo (non-OIDC providers like GitHub).
  3. Returning a canonical Identity with EmailVerified guaranteed true.

Errors returned to callers are intentionally generic — they do not leak provider response bodies. Callers that need provider-specific debugging should inspect logs.

func NewApple added in v1.5.0

func NewApple(cfg AppleConfig) Exchanger

NewApple returns an Exchanger that implements Sign-In with Apple.

func NewGitHub

func NewGitHub(cfg GitHubConfig) Exchanger

NewGitHub returns an Exchanger for GitHub. Note that GitHub does not implement OIDC; this exchanger calls the user/userEmails APIs directly using the access token returned by the token endpoint.

func NewGoogle

func NewGoogle(cfg GoogleConfig) Exchanger

NewGoogle returns an Exchanger for Google OIDC.

func NewMicrosoft

func NewMicrosoft(cfg MicrosoftConfig) Exchanger

NewMicrosoft returns an Exchanger for Microsoft Azure AD.

type GitHubConfig

type GitHubConfig struct {
	ClientID     string
	ClientSecret string

	HTTPClient *http.Client

	AuthorizationURL string
	TokenURL         string
	UserURL          string
	UserMailURL      string
}

GitHubConfig configures a GitHub Exchanger.

type GoogleConfig

type GoogleConfig struct {
	ClientID     string
	ClientSecret string

	// HTTPClient overrides the http.Client used for token + JWKS
	// requests. Optional; defaults to a 10s-timeout client.
	HTTPClient *http.Client

	// TokenURL overrides the token endpoint. Optional; defaults to
	// googleTokenURL.
	TokenURL string

	// AuthorizationURL overrides the provider authorization endpoint.
	// Optional; defaults to googleAuthorizationURL or the discovery
	// document's authorization_endpoint when DiscoveryURL is set.
	AuthorizationURL string

	// JWKSURL overrides the JWKS endpoint. Optional; defaults to
	// googleJWKSURL.
	JWKSURL string

	// DiscoveryURL overrides the OIDC discovery endpoint. When set,
	// Exchange resolves token / JWKS / userinfo endpoints from it.
	DiscoveryURL string

	// UserinfoURL overrides the OIDC userinfo endpoint. Optional.
	UserinfoURL string

	// Issuer overrides the expected `iss` claim. Optional; defaults
	// to googleIssuer.
	Issuer string

	// JWKSCacheTTL overrides the JWKS cache TTL. Optional; defaults
	// to 1h.
	JWKSCacheTTL time.Duration

	// Now overrides the clock used for ID token expiry validation.
	// Optional; defaults to time.Now.
	Now func() time.Time
}

GoogleConfig configures a Google Exchanger. ClientID and ClientSecret are required; the rest default to the live Google endpoints and a 1h JWKS cache.

type HostedStateClaims added in v0.9.0

type HostedStateClaims struct {
	Provider     string
	RedirectURI  string
	ReturnTo     string
	State        string
	CodeVerifier string
	CSRFToken    string
	IssuedAt     int64
	ExpiresAt    int64
}

HostedStateClaims are the tamper-proof claims the hosted OAuth flow binds into the state token it carries through the provider redirect. It is a superset of the headless StateClaims with the app's return_to added: the provider sends only `state` + `code` back to the single callback URL, so everything else (the PKCE verifier, the provider name, and where to hand the user back) must round-trip inside this signed artifact.

This is a SEPARATE artifact from the headless state token (state.go). The headless flow's IssueStateToken / VerifyStateToken shape is intentionally left untouched — the SPA still round-trips the verifier itself there — so the hosted return_to binding does not become a breaking change for native/mobile callers (#126 stop condition).

func VerifyHostedStateToken added in v0.9.0

func VerifyHostedStateToken(
	token string,
	kp identityjwt.KeyProvider,
	now time.Time,
) (*HostedStateClaims, error)

VerifyHostedStateToken validates the signature and expiry of the hosted state token (which IS the OAuth `state` parameter the provider echoed back), then returns the recovered claims. The provider, return target, and PKCE verifier are recovered from the token — the callback has no other source for them. The token's signature is the CSRF binding: an attacker cannot forge a state the server will accept.

type Identity

type Identity struct {
	// ProviderUserID is the stable per-provider user identifier.
	// For OIDC providers this is the "sub" claim. For GitHub this is
	// the numeric user id (rendered as a string).
	ProviderUserID string

	// Email is the user's primary email address. Always lowercased.
	Email string

	// EmailVerified is true if the provider asserts the email is
	// verified. Implementations MUST refuse to return an Identity if
	// the provider says the email is not verified.
	EmailVerified bool

	// Name is the user's display name. May be empty.
	Name string

	// AvatarURL is a URL to the user's profile picture. May be empty.
	AvatarURL string

	// Provider is the provider key — "google", "microsoft", "github", "apple".
	Provider string
}

Identity is the canonical, verified user identity returned by an Exchanger after a successful code exchange. Implementations MUST only return an Identity for verified users — i.e. the caller can rely on Email/ProviderUserID being authoritative.

type MicrosoftConfig

type MicrosoftConfig struct {
	ClientID     string
	ClientSecret string

	HTTPClient *http.Client

	// AuthorizationURL overrides the authorization endpoint. Optional.
	AuthorizationURL string

	// TokenURL overrides the token endpoint. Optional.
	TokenURL string

	// JWKSURL overrides the JWKS endpoint. Optional.
	JWKSURL string

	// TenantID controls the default tenant segment in the authorization
	// endpoint when AuthorizationURL is not set. Optional; defaults to
	// "common".
	TenantID string

	// IssuerFormat is a fmt.Sprintf format string into which the
	// token's `tid` (tenant id) claim is interpolated to derive the
	// expected issuer. Optional; defaults to the Microsoft format.
	// In tests, set this to e.g. "%s" plus a fixed test issuer.
	IssuerFormat string

	JWKSCacheTTL time.Duration
	Now          func() time.Time
}

MicrosoftConfig configures a Microsoft Azure AD Exchanger.

type Registry

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

Registry maps provider keys ("google", "microsoft", "github", "apple") to their Exchanger implementations. The service layer looks up the Exchanger for the provider named in the OAuthLoginRequest.

A nil *Registry is valid and reports every provider as missing. This lets the service treat "OAuth login disabled" as a registry without any registered providers (or no registry at all).

func NewRegistry

func NewRegistry() *Registry

NewRegistry returns an empty registry. Callers populate it with Register.

func (*Registry) Get

func (r *Registry) Get(provider string) (Exchanger, bool)

Get returns the Exchanger for the given provider key. The second return value is false if no Exchanger is registered.

func (*Registry) Len

func (r *Registry) Len() int

Len reports how many providers are registered.

func (*Registry) Providers

func (r *Registry) Providers() []string

Providers returns the sorted list of currently-registered provider keys. Useful for startup logging.

func (*Registry) Register

func (r *Registry) Register(provider string, e Exchanger)

Register associates the given Exchanger with a provider key. Calling Register with the same key twice replaces the previous entry. A nil Exchanger is treated as "unregister" so callers can disable a provider at runtime.

type StateClaims

type StateClaims struct {
	Provider     string
	RedirectURI  string
	State        string
	CodeVerifier string
	IssuedAt     int64
	ExpiresAt    int64
}

func VerifyStateToken

func VerifyStateToken(
	token string,
	kp identityjwt.KeyProvider,
	expectedProvider, expectedRedirectURI, returnedState, explicitCodeVerifier string,
	now time.Time,
) (*StateClaims, error)

Jump to

Keyboard shortcuts

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