oidcx

package
v0.17.3 Latest Latest
Warning

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

Go to latest
Published: Jul 31, 2026 License: Apache-2.0 Imports: 10 Imported by: 0

Documentation

Overview

Package oidcx provides an auto-refreshing JWK Set cache that survives IdP key rotation without restarting the host process. It is a thin, import-light wrapper around jwx v2's jwk.Cache that:

  • registers a single JWKS URL with sane defaults (15-minute minimum refresh interval, refresh-on-failure semantics);
  • exposes a Set(ctx) accessor that callers use on every JWT verification path so that key rotation is picked up without polling or process restart;
  • emits structured failure callbacks via an optional Logger hook so that obsx (or any other observability package) can audit refresh errors without oidcx pulling obsx in as a dependency.

The cache is safe for concurrent use; jwk.Cache serializes refresh requests internally.

Index

Constants

View Source
const DefaultFetchTimeout = 30 * time.Second

DefaultFetchTimeout is the default per-request timeout applied to the HTTP client used to fetch the JWKS document. APPSEC-7: prior versions inherited http.DefaultClient (no timeout), so a hung IdP could stall cold-start, refresh goroutines, and (once wired) every sync JWT verification. 30 seconds is conservative enough to cover slow-but-healthy IdPs while still bounding the worst case.

View Source
const DefaultMaxStaleness = 0

DefaultMaxStaleness is 0, which disables the staleness cap. Callers that want a hard upper bound on how stale the cache may become on extended IdP outages must opt in via WithMaxStaleness. APPSEC-3.

View Source
const DefaultMinRefreshInterval = 15 * time.Minute

DefaultMinRefreshInterval is the floor for re-fetching the JWKS, regardless of any Cache-Control / Expires hint supplied by the IdP. 15 minutes balances rotation responsiveness against IdP load.

Variables

View Source
var ErrEmptyJWKSURL = errors.New("oidcx: empty JWKS URL")

ErrEmptyJWKSURL is returned by NewJWKCache when the supplied JWKS URL is empty. Distinct from ErrEmptyJWKSet which signals that a fetch succeeded but returned no keys.

View Source
var ErrEmptyJWKSet = errors.New("oidcx: jwks endpoint returned no keys")

ErrEmptyJWKSet is returned by NewJWKCache when the IdP responds with a parseable but empty JWK set on cold start. Callers should treat this as a misconfiguration of the JWKS endpoint.

View Source
var ErrJWKSStaleExceeded = errors.New("oidcx: jwks cache exceeded max staleness")

ErrJWKSStaleExceeded is returned by JWKCache.Set when the most recent successful fetch is older than the configured WithMaxStaleness window. Callers should treat this as a hard authentication failure rather than fall back to the cached set: jwk.AutoRefresh would otherwise serve the stale set indefinitely during an extended IdP outage. APPSEC-3.

View Source
var ErrNilJWKCache = errors.New("oidcx: nil JWKCache")

ErrNilJWKCache is returned by methods on a nil receiver. This is strictly a defense against caller error -- production code should always inject a non-nil cache.

Functions

This section is empty.

Types

type HTTPClient

type HTTPClient interface {
	Do(*http.Request) (*http.Response, error)
}

HTTPClient is the minimal interface oidcx needs to fetch JWKS over HTTP. Tests and callers using a non-default transport (mTLS, proxy, custom timeout) inject their own client via WithHTTPClient.

The signature deliberately mirrors *http.Client.Do so callers can pass *http.Client or any wrapper (e.g. an mTLS or instrumented client) that already satisfies that contract. Internally the cache adapts this to jwx v2's Get(url string)-shaped HTTPClient interface; callers don't need to think about that.

type JWKCache

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

JWKCache is an auto-refreshing JWK Set cache. The zero value is not usable; construct one with NewJWKCache.

func NewJWKCache

func NewJWKCache(ctx context.Context, jwksURL string, opts ...Option) (*JWKCache, error)

NewJWKCache creates a cache for the given JWKS URL and primes it by performing an initial Refresh against the IdP. It returns an error if the cold-start fetch fails or the response is empty. The supplied context governs only the cold-start fetch; once the cache is constructed, refreshes use a context derived from the host process.

func (*JWKCache) Healthy

func (c *JWKCache) Healthy() bool

Healthy reports whether the cache is within the configured max-staleness window. Always true when WithMaxStaleness was not supplied. APPSEC-3.

func (*JWKCache) Refresh

func (c *JWKCache) Refresh(ctx context.Context) (jwk.Set, error)

Refresh forces an immediate refresh of the cached JWK set. Intended for tests, key-rotation event handlers, and admin-triggered refresh endpoints. Production paths should rely on Set's auto-refresh.

Errors from the underlying refresh are also reported to any logger supplied via WithLogger so the caller's observability pipeline sees the same failure surface that the background refresher emits via the ErrSink. jwx v2's synchronous Refresh returns the error directly rather than forwarding to the ErrSink, so without this hop the logger would miss manual refresh failures and admin-triggered retries.

func (*JWKCache) Set

func (c *JWKCache) Set(ctx context.Context) (jwk.Set, error)

Set returns the current JWK set, triggering a refresh if the configured interval has elapsed. Per AutoRefresh semantics: if a refresh is in progress, callers receive the cached set, not a stale error. If the refresh fails, the cache continues to serve the last-good set so authentication does not flap during transient IdP outages -- subject to the WithMaxStaleness cap, which when set causes Set to return ErrJWKSStaleExceeded rather than indefinitely extend the trust window. APPSEC-3.

func (*JWKCache) URL

func (c *JWKCache) URL() string

URL reports the JWKS URL the cache is bound to.

type Logger

type Logger interface {
	Warn(msg string, args ...any)
	Error(msg string, args ...any)
}

Logger is the structured logging contract the cache uses to surface refresh failures and stale-fallback events. Callers wire their own logger (slog, obsx audit, log.Logger) by passing WithLogger. Keeping this an interface rather than a concrete type lets oidcx stay free of any logging dependency.

type Option

type Option func(*config)

Option configures a JWKCache at construction time.

func WithFetchTimeout

func WithFetchTimeout(d time.Duration) Option

WithFetchTimeout overrides the per-request HTTP timeout applied when fetching the JWKS document. Defaults to DefaultFetchTimeout. Values <= 0 fall back to the default rather than disabling the timeout: a permanently-disabled timeout was the APPSEC-7 root cause and is not user-recoverable from the cache contract. Callers that need an unusual timeout policy should inject their own http.Client via WithHTTPClient; that client's Timeout is preserved verbatim. APPSEC-7.

func WithHTTPClient

func WithHTTPClient(c HTTPClient) Option

WithHTTPClient configures the underlying HTTP client used to fetch the JWKS document. Defaults to http.DefaultClient via jwx.

func WithLogger

func WithLogger(l Logger) Option

WithLogger registers a structured logger that receives a Warn entry on every refresh failure followed by an Error entry if the cache cannot serve any prior key set (cold-start failure).

func WithMaxStaleness

func WithMaxStaleness(d time.Duration) Option

WithMaxStaleness configures the upper bound on how stale the cached JWK set may become before JWKCache.Set starts returning ErrJWKSStaleExceeded. Counted from the last successful fetch. Zero (the default) disables the cap, preserving the legacy "serve last-good indefinitely" behaviour. Operators running security-sensitive workloads should set this to a value larger than the JWKS rotation cadence but smaller than the access-token lifetime so an extended IdP outage cannot indefinitely extend the trust window. APPSEC-3.

func WithMinRefreshInterval

func WithMinRefreshInterval(d time.Duration) Option

WithMinRefreshInterval overrides DefaultMinRefreshInterval. Values below 1 second are clamped to 1 second to avoid pathological refresh storms; values above 24 hours are accepted as-is so operators can extend the floor for tightly cost-controlled environments.

Jump to

Keyboard shortcuts

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