oidcclient

package
v0.6.3 Latest Latest
Warning

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

Go to latest
Published: Sep 12, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

Documentation

Overview

Package oidcclient provides a provider-neutral OIDC client used across all kombify Go services (Simulate, StackKits-Server, TechStack).

The package is split into four pieces that compose:

  • Verifier — verifies RS256 ID tokens against issuer + audience using a cached JWKS endpoint.
  • Provider / Registry — wraps a Verifier together with the OAuth2 authorization-code endpoints of a single issuer (Pocket ID, Auth0, PocketBase-OIDC bridge, generic OIDC).
  • CodeExchanger — exchanges an authorization code (with optional PKCE verifier) for an ID token.
  • Discover — best-effort fetch of `.well-known/openid-configuration`.

Identity bridge: IdentityFromClaims converts an OIDC Claims into a github.com/kombifyio/go-common/identity.Identity so that downstream middleware can keep using the existing identity context plumbing.

Self-hosted vs SaaS: callers configure the same client with a different Issuer URL — Pocket ID for the self-hosted default, Auth0 for SaaS, the PocketBase OIDC bridge for legacy operators. There are no SaaS-specific branches in this package.

Index

Constants

View Source
const (
	DefaultJWKSRefreshMin = 30 * time.Second
	DefaultJWKSRefreshMax = 15 * time.Minute
	DefaultHTTPTimeout    = 5 * time.Second
)

Defaults for JWKS cache behaviour. Min interval throttles upstream JWKS hits when a token references an unknown KID (could be attacker-driven). Max interval forces periodic refresh even if no cache miss occurs.

Variables

View Source
var (
	ErrUnknownProvider = errors.New("oidcclient: unknown provider")
	ErrInvalidProvider = errors.New("oidcclient: invalid provider configuration")
)

Errors returned by Registry / Provider / PKCE helpers.

View Source
var (
	ErrInvalidToken     = errors.New("oidcclient: invalid token")
	ErrUnknownKID       = errors.New("oidcclient: unknown key id")
	ErrUnsupportedAlg   = errors.New("oidcclient: unsupported signing algorithm")
	ErrIssuerMismatch   = errors.New("oidcclient: issuer mismatch")
	ErrAudienceMismatch = errors.New("oidcclient: audience mismatch")
	ErrTokenExpired     = errors.New("oidcclient: token expired")
	ErrTokenNotYetValid = errors.New("oidcclient: token not yet valid")
	ErrJWKSFetchFailed  = errors.New("oidcclient: jwks fetch failed")
	ErrConfigInvalid    = errors.New("oidcclient: configuration invalid")
)

Errors returned by Verify.

View Source
var ErrDiscoveryFailed = errors.New("oidcclient: discovery failed")

ErrDiscoveryFailed is returned when `.well-known/openid-configuration` is unreachable or malformed.

View Source
var (
	ErrTokenExchange = errors.New("oidcclient: token exchange failed")
)

Errors returned by code exchange.

Functions

func ApplyDiscovery

func ApplyDiscovery(cfg *ProviderConfig, doc *DiscoveryDocument)

ApplyDiscovery fills the URL fields of cfg from doc when they are unset. It is a convenience used after Discover:

doc, _ := oidcclient.Discover(ctx, cfg.Issuer, nil)
oidcclient.ApplyDiscovery(&cfg, doc)
provider, _ := oidcclient.NewProvider(cfg)

func IdentityFromClaims

func IdentityFromClaims(c *Claims) *identity.Identity

IdentityFromClaims projects an OIDC Claims onto a identity.Identity. The mapping is intentionally simple — services that need richer mapping (per-tenant role tables, group→role rewrites) should post-process the returned value.

Claim sources, in priority order:

  • UserID ← `sub`
  • Email ← `email`
  • OrgID ← `org_id` || `org` || `project_id` (raw claim)
  • Tier ← `tier` || `plan` (raw claim)
  • Roles ← `roles` (raw claim, []string or comma-separated string) || single `role` claim

Returns nil if claims is nil.

func PKCEVerifier

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

PKCEVerifier returns a fresh RFC-7636 PKCE verifier (43–128 chars, base64url unpadded random bytes) plus its S256 challenge. Callers persist the verifier server-side keyed by login-state and pass the challenge to Provider.AuthCodeURL.

Types

type Claims

type Claims struct {
	Subject  string                 `json:"sub"`
	Issuer   string                 `json:"iss"`
	Audience []string               `json:"aud"`
	Email    string                 `json:"email,omitempty"`
	Name     string                 `json:"name,omitempty"`
	IssuedAt int64                  `json:"iat,omitempty"`
	Expires  int64                  `json:"exp,omitempty"`
	Raw      map[string]interface{} `json:"-"`
}

Claims is the verified subset of an ID token. Provider-specific claims can be re-decoded by callers from Claims.Raw.

type CodeExchangeRequest

type CodeExchangeRequest struct {
	Code         string
	RedirectURI  string
	PKCEVerifier string
}

CodeExchangeRequest carries the parameters needed for an authorization-code exchange. PKCEVerifier is optional but strongly recommended for public clients (no ClientSecret).

type CodeExchangeResult

type CodeExchangeResult struct {
	IDToken      string `json:"id_token"`
	AccessToken  string `json:"access_token,omitempty"`
	RefreshToken string `json:"refresh_token,omitempty"`
	TokenType    string `json:"token_type,omitempty"`
	ExpiresIn    int64  `json:"expires_in,omitempty"`
	Scope        string `json:"scope,omitempty"`
}

CodeExchangeResult is the parsed token endpoint response. Refresh- and access-token are exposed for callers that need them; the typical kombify flow only consumes IDToken.

type CodeExchanger

type CodeExchanger interface {
	ExchangeCode(ctx context.Context, p *Provider, req CodeExchangeRequest) (*CodeExchangeResult, error)
}

CodeExchanger exchanges an OAuth2 authorization code for an ID token.

Implementations must be safe for concurrent use. The default HTTPCodeExchanger talks RFC-6749 directly to Provider.TokenURL.

type DiscoveryDocument

type DiscoveryDocument struct {
	Issuer                string   `json:"issuer"`
	AuthorizationEndpoint string   `json:"authorization_endpoint"`
	TokenEndpoint         string   `json:"token_endpoint"`
	JWKSURI               string   `json:"jwks_uri"`
	UserinfoEndpoint      string   `json:"userinfo_endpoint,omitempty"`
	EndSessionEndpoint    string   `json:"end_session_endpoint,omitempty"`
	ScopesSupported       []string `json:"scopes_supported,omitempty"`
}

DiscoveryDocument is the subset of the OIDC discovery metadata kombify services consume. The full RFC-defined document has many more fields; we only surface what we use.

func Discover

func Discover(ctx context.Context, issuer string, client *http.Client) (*DiscoveryDocument, error)

Discover fetches `<issuer>/.well-known/openid-configuration` and returns the parsed metadata. Use it on startup to populate ProviderConfig.AuthorizationURL / ProviderConfig.TokenURL / ProviderConfig.JWKSURL without hard-coding endpoint paths.

The HTTP client defaults to a 5 s timeout when nil.

type HTTPCodeExchanger

type HTTPCodeExchanger struct {
	Client *http.Client
}

HTTPCodeExchanger is the default RFC-6749 code-exchanger.

func NewHTTPCodeExchanger

func NewHTTPCodeExchanger() *HTTPCodeExchanger

NewHTTPCodeExchanger returns an exchanger with a sane default timeout.

func (*HTTPCodeExchanger) ExchangeCode

ExchangeCode posts an `application/x-www-form-urlencoded` token request to the provider's token endpoint. ClientSecret (if present) is sent as HTTP Basic auth. PKCE verifier is sent as a form field.

type Kind

type Kind string

Kind enumerates the supported provider flavors. The kind drives default discovery URL composition; once a provider is constructed all kinds share the same OIDC verification path.

const (
	// KindPocketID is the kombify self-hosted default identity provider
	// (passkey-first OIDC).
	KindPocketID Kind = "pocketid"
	// KindAuth0 is the kombify SaaS identity provider via Cloudflare Edge
	// Router.
	KindAuth0 Kind = "auth0"
	// KindPocketBase is the optional self-hosted alternative — a dedicated
	// PocketBase instance behind the kombify OIDC bridge.
	KindPocketBase Kind = "pocketbase"
	// KindGeneric is any other RFC-compliant OIDC issuer.
	KindGeneric Kind = "generic"
)

type Provider

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

Provider is a verified-OIDC bridge for a single identity provider config.

func NewProvider

func NewProvider(cfg ProviderConfig) (*Provider, error)

NewProvider builds a Provider from a ProviderConfig. JWKSURL defaults to `<issuer>/.well-known/jwks.json` when not explicitly set; that is the convention for both Pocket ID and Auth0. Use Discover to populate the URL fields from `.well-known/openid-configuration`.

func (*Provider) AuthCodeURL

func (p *Provider) AuthCodeURL(redirectURI, state, codeChallenge string) string

AuthCodeURL constructs the user-agent redirect URL for starting an auth-code flow. When codeChallenge is non-empty PKCE (S256) is added.

func (*Provider) AuthorizationURL

func (p *Provider) AuthorizationURL() string

AuthorizationURL returns the provider authorization endpoint.

func (*Provider) ClientID

func (p *Provider) ClientID() string

ClientID returns the OAuth2 client id used for auth-code login.

func (*Provider) ClientSecret

func (p *Provider) ClientSecret() string

ClientSecret returns the OAuth2 client secret, if configured.

func (*Provider) ID

func (p *Provider) ID() string

ID returns the logical provider id.

func (*Provider) Issuer

func (p *Provider) Issuer() string

Issuer returns the provider issuer URL.

func (*Provider) Kind

func (p *Provider) Kind() Kind

Kind returns the provider flavor.

func (*Provider) Scopes

func (p *Provider) Scopes() []string

Scopes returns the default scopes used in auth-code requests.

func (*Provider) TokenURL

func (p *Provider) TokenURL() string

TokenURL returns the provider token endpoint.

func (*Provider) Verifier

func (p *Provider) Verifier() *Verifier

Verifier returns the underlying ID-token verifier (rarely needed by callers, exposed for tests and advanced flows).

func (*Provider) Verify

func (p *Provider) Verify(ctx context.Context, rawToken string) (*Claims, error)

Verify delegates to the underlying OIDC verifier.

type ProviderConfig

type ProviderConfig struct {
	ID               string   // logical id (per org/tenant), e.g. "primary", "auth0-saas"
	Kind             Kind     // provider flavor (drives default URL composition)
	Issuer           string   // required
	Audience         string   // optional; defaults to ClientID
	ClientID         string   // required for auth code login
	ClientSecret     string   // optional for public clients (use PKCE instead)
	AuthorizationURL string   // optional; derived from issuer when empty
	TokenURL         string   // optional; derived from issuer when empty
	JWKSURL          string   // optional; derived from issuer when empty
	Scopes           []string // optional; defaults to openid/profile/email
}

ProviderConfig describes a single identity provider entry.

type Registry

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

Registry holds providers indexed by ID. Safe for concurrent reads after construction; write methods take an internal mutex.

func NewRegistry

func NewRegistry() *Registry

NewRegistry returns an empty registry.

func (*Registry) Add

func (r *Registry) Add(p *Provider)

Add registers (or replaces) a provider.

func (*Registry) Get

func (r *Registry) Get(id string) (*Provider, error)

Get returns the provider with the given id.

func (*Registry) IDs

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

IDs returns the registered provider ids in arbitrary order.

func (*Registry) Len

func (r *Registry) Len() int

Len returns the number of registered providers.

type Verifier

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

Verifier verifies OIDC ID tokens issued by a single issuer.

func NewVerifier

func NewVerifier(cfg VerifierConfig) (*Verifier, error)

NewVerifier constructs a Verifier and validates required config.

func (*Verifier) Verify

func (v *Verifier) Verify(ctx context.Context, rawToken string) (*Claims, error)

Verify parses and validates the given raw ID token. On success the returned Claims are guaranteed to match issuer + audience and to be within exp/nbf bounds (with VerifierConfig.ClockSkew tolerance).

type VerifierConfig

type VerifierConfig struct {
	Issuer         string
	Audience       string
	JWKSURL        string
	HTTPClient     *http.Client
	JWKSRefreshMin time.Duration
	JWKSRefreshMax time.Duration
	ClockSkew      time.Duration
}

VerifierConfig configures a Verifier.

Jump to

Keyboard shortcuts

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