oidc

package
v0.18.1 Latest Latest
Warning

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

Go to latest
Published: Aug 20, 2026 License: Apache-2.0 Imports: 19 Imported by: 0

Documentation

Overview

Package oidc implements an auth.Provider that verifies JWT bearer tokens against an OpenID Connect issuer.

It works with any compliant OIDC provider — Auth0, Keycloak, Azure AD, Google Workspace, Ping, JumpCloud, and (in OIDC-only mode) Okta.

Behavior at a glance:

  • On first verify, the discovery document is fetched once from {issuer}/.well-known/openid-configuration and cached forever (the issuer is stable per OIDC spec).

  • JWKS is fetched lazily and cached. On unknown-kid, the JWKS is refetched once before declaring the key unknown.

  • The signing algorithm is taken from the JWKS entry, NOT from the token header. This defends against algorithm-confusion attacks (e.g., a token claiming `alg: HS256` against an RSA JWKS).

  • alg=none and HMAC algorithms (HS256/384/512) are never accepted.

  • Standard claims are validated: iss exact match, aud contains configured Audience (with optional azp == ClientID fallback), exp/nbf within ClockSkew leeway.

  • Claim → Identity mapping is configurable via ClaimMap. The X-Org-ID header overrides the claim-derived OrgID for per-request tenant routing.

Index

Constants

View Source
const (
	DefaultClockSkew   = 30 * time.Second
	DefaultHTTPTimeout = 10 * time.Second
)

Default operational tunables.

View Source
const ProviderName = "oidc"

ProviderName is the type name used to register and reference this provider in the auth registry.

Variables

View Source
var ErrKeyNotFound = fmt.Errorf("oidc: signing key not found")

ErrKeyNotFound is returned when a `kid` is not in the cache and a refresh did not produce it.

Functions

This section is empty.

Types

type ClaimMap

type ClaimMap struct {
	UserID      string `yaml:"user_id,omitempty"`
	Email       string `yaml:"email,omitempty"`
	OrgID       string `yaml:"org_id,omitempty"`
	WorkspaceID string `yaml:"workspace_id,omitempty"`
	Groups      string `yaml:"groups,omitempty"`
}

ClaimMap configures which JWT claim names are read into each Identity field. Empty fields fall back to OIDC standard claim names.

type Config

type Config struct {
	// Issuer is the full OIDC issuer URL (no trailing slash). Required.
	// The token's `iss` claim must match this value exactly.
	Issuer string `yaml:"issuer"`

	// Audience is the expected `aud` claim value. Required. If the token
	// has multiple audiences, the configured Audience must be one of them.
	Audience string `yaml:"audience"`

	// ClientID is an optional secondary audience check: if set, a token
	// whose `aud` does not contain Audience is still accepted when its
	// `azp` (authorized party) claim equals ClientID.
	ClientID string `yaml:"client_id,omitempty"`

	// JWKSURL overrides the JWKS endpoint discovered via the OIDC
	// discovery document. Most users leave this empty.
	JWKSURL string `yaml:"jwks_url,omitempty"`

	// JWKSCacheTTL caps the maximum age of cached JWKS keys. Defaults to
	// 1 hour. Values below 5 minutes are silently clamped up to avoid
	// hammering the IdP.
	JWKSCacheTTL time.Duration `yaml:"jwks_cache_ttl,omitempty"`

	// ClockSkew is the leeway applied to `exp` and `nbf` validation.
	// Defaults to 30 seconds.
	ClockSkew time.Duration `yaml:"clock_skew,omitempty"`

	// ClaimMap configures which JWT claim names map to Identity fields.
	// Empty fields use OIDC defaults (sub, email, org_id, …).
	ClaimMap ClaimMap `yaml:"claim_map,omitempty"`

	// HTTPClient overrides the default client. Injectable for tests.
	HTTPClient *http.Client `yaml:"-"`

	// SkipIssuerCheck disables the iss-claim equality check. INTERNAL —
	// the yaml:"-" tag means this CANNOT be set via forge.yaml; it is
	// only reachable when another Go package constructs oidc.Config
	// directly (currently only azure_ad's multi-tenant mode).
	//
	// Reason it exists: AAD's "common" / multi-tenant issuer template
	// uses a per-token tenant ID that string-equality can't satisfy. The
	// caller (azure_ad) takes responsibility for tenant enforcement via
	// the tid claim instead. Surfacing this in forge.yaml would let
	// operators disable iss validation by accident — which is exactly
	// the "open verifier" footgun this package is designed to prevent.
	SkipIssuerCheck bool `yaml:"-"`
}

Config controls the OIDC provider.

func (Config) Validate

func (c Config) Validate() error

Validate returns ErrProviderNotConfigured when required fields are missing.

type Provider

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

Provider implements auth.Provider for OIDC issuers.

func New

func New(cfg Config) (*Provider, error)

New constructs a Provider after validating cfg. No network I/O happens here — discovery and JWKS are fetched lazily on first Verify.

func (*Provider) Name

func (p *Provider) Name() string

Name implements auth.Provider.

func (*Provider) Verify

func (p *Provider) Verify(ctx context.Context, tokenStr string, headers auth.Headers) (*auth.Identity, error)

Verify implements auth.Provider.

The verification flow:

  1. Parse token structurally (without signature verification yet).
  2. Extract kid from header; reject tokens without kid.
  3. Look up kid in JWKS cache (refreshing on miss).
  4. Cross-check token's `alg` against JWKS-declared alg (defends against algorithm confusion).
  5. Re-parse the token with the resolved key, validating iss/exp/nbf.
  6. Validate audience (with azp fallback if ClientID is configured).
  7. Map claims to Identity, applying header overrides.

Jump to

Keyboard shortcuts

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