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 ¶
const ( DefaultClockSkew = 30 * time.Second DefaultHTTPTimeout = 10 * time.Second )
Default operational tunables.
const ProviderName = "oidc"
ProviderName is the type name used to register and reference this provider in the auth registry.
Variables ¶
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.
type Provider ¶
type Provider struct {
// contains filtered or unexported fields
}
Provider implements auth.Provider for OIDC issuers.
func New ¶
New constructs a Provider after validating cfg. No network I/O happens here — discovery and JWKS are fetched lazily on first Verify.
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:
- Parse token structurally (without signature verification yet).
- Extract kid from header; reject tokens without kid.
- Look up kid in JWKS cache (refreshing on miss).
- Cross-check token's `alg` against JWKS-declared alg (defends against algorithm confusion).
- Re-parse the token with the resolved key, validating iss/exp/nbf.
- Validate audience (with azp fallback if ClientID is configured).
- Map claims to Identity, applying header overrides.