oauth

package
v0.3.3 Latest Latest
Warning

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

Go to latest
Published: May 9, 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 IssueStateToken

func IssueStateToken(
	kr *identityjwt.KeyRing,
	provider, redirectURI, state, codeVerifier string,
	expiry time.Duration,
	now time.Time,
) (string, error)

func WithCodeVerifier

func WithCodeVerifier(ctx context.Context, codeVerifier string) context.Context

WithCodeVerifier carries the request-scoped PKCE verifier to the provider token exchange.

Types

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 Exchanger

type Exchanger interface {
	Exchange(ctx context.Context, code, redirectURI string) (*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 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 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".
	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") 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,
	kr *identityjwt.KeyRing,
	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