oidc

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Aug 25, 2026 License: MIT Imports: 34 Imported by: 0

README

OIDC ID-token validation

oidc discovers an OpenID Provider and validates signed OpenID Connect ID tokens. It owns the authentication trust boundary only: applications retain OAuth authorization flows, redirects, sessions, cookies, middleware, nonce storage, and authorization policy.

The module implements the OpenID Connect Core 1.0 ID-token validation rules and OpenID Connect Discovery 1.0 provider metadata rules. It supports the authorization-code, implicit, and hybrid ID-token profiles with asymmetric RS*, PS*, ES*, and EdDSA signatures. The provider MUST advertise every configured algorithm and MUST advertise RS256, as required by Discovery. Symmetric client-secret signatures, encrypted ID tokens, distributed claims, OAuth flow execution, UserInfo, logout, dynamic registration, and access-token validation are deliberately excluded.

Install

go get github.com/faustbrian/go-authentication/oidc

Setup

validator, err := oidc.New(ctx, oidc.Config{
	Issuer:     "https://accounts.example.com/tenant",
	ClientID:   "web-client",
	Algorithms: []string{"RS256"},
	Clock:      clock.System{},
	NonceValidator: oidc.NonceValidatorFunc(
		func(ctx context.Context, nonce string) error {
			return nonceStore.Consume(ctx, nonce)
		},
	),
})
if err != nil {
	return err
}

principal, err := validator.ValidateIDToken(ctx, rawIDToken, oidc.TokenBinding{
	AccessToken:       accessToken,
	AuthorizationCode: authorizationCode,
})

New reads <issuer>/.well-known/openid-configuration, validates the returned issuer and metadata, and eagerly fetches the initial JWKS. An issuer with a path follows the Discovery append rule; the configured and returned issuer strings and the token iss claim must match exactly.

ValidateBearer validates an ID token without front-channel hash inputs. ValidateIDToken additionally validates at_hash and c_hash for each non-empty TokenBinding field. Hash binding is supported for the *256, *384, and *512 families; an EdDSA token with a requested binding is rejected because that algorithm name defines no OIDC half-hash selection. Authenticate adapts the same validation to the parent authentication package's bearer-credential contract.

Configuration

Configuration is copied into a Validator; later changes to slices or to the Config value do not change it. Caller-supplied collaborators (Clock, NonceValidator, and an HTTPClient transport) must themselves be safe for concurrent use and must not be mutated after construction.

Important options and defaults:

Option Meaning Default
Issuer Exact provider issuer and discovery base required
ClientID Required audience and azp value required
TrustedAudiences Additional audience values allowed beside the client ID none
Algorithms Explicit asymmetric ID-token algorithm allowlist required
ClockSkew Symmetric temporal tolerance 5 minutes
MaxTokenBytes Compact-token size limit 16 KiB
MaxClaims, MaxClaimDepth JSON member and nesting limits authentication package limits
MaxHTTPBodyBytes Decompressed discovery/JWKS body limit 1 MiB
DiscoveryTimeout Initialization discovery/JWKS deadline 10 seconds
MaxKeys JWKS key-count limit 64
MinRefreshInterval, MaxRefreshInterval Refresh and cache bounds 1 minute / 1 hour
MaxRefreshWaiters Concurrent callers admitted to a refresh 64

Configuration also enforces hard ceilings of 1 MiB per token, 16 MiB per HTTP body, 4,096 keys, 4,096 refresh waiters, five minutes for initialization, and 24 hours for refresh intervals. A supplied HTTP client may shorten the request timeout but cannot extend the module's 30-second per-request ceiling when its transport honors request-context cancellation. Custom transports own that cancellation guarantee.

HTTPS is required for the issuer and provider endpoints. InsecureHTTP is a loopback-only development exception; non-loopback HTTP is rejected and it is not a production downgrade switch.

Nonce ownership

Nonce generation and replay storage remain caller-owned. Supplying a NonceValidator means every otherwise accepted token is passed to that callback after signature, token-hash, and claim validation. The callback receives the validation context and the raw nonce, and should atomically consume a single-use value. An empty, unknown, expired, replayed, or otherwise invalid nonce should return an error. The validator does not retain the nonce. Callback errors and panics are converted to a credential rejection and their text is not exposed; callback cancellation is authentication unavailability and retains the context sentinel.

Validation policy

Validation requires:

  • a permitted signature algorithm and a matching public JWK;
  • one exact JSON response media type, unique and correctly typed metadata/JWK members, a non-empty operation policy when key_ops is present, and a kid whenever multiple permitted signature keys remain after unrelated encryption keys are ignored;
  • RSA public keys between 2,048 and 8,192 bits, or the exact curve/key shape required by the configured ECDSA or EdDSA algorithm;
  • exact issuer equality;
  • a non-empty ASCII subject no longer than 255 bytes;
  • the client ID in aud, no duplicate audience, and no audience outside ClientID plus TrustedAudiences;
  • azp equal to the client ID whenever present, and present for multiple audiences;
  • required iat and exp, with optional nbf and auth_time checked using the configured clock and skew without discarding fractional seconds;
  • correctly typed standard JOSE headers and optional protocol claims, and no unsupported distributed-claim protocol fields;
  • the caller-owned nonce check when configured; and
  • at_hash and c_hash when their corresponding binding values are supplied.

Registered protocol claims, configured scope claims, and configured tenant claims are not copied into the principal's arbitrary claim map. Scope and tenant values are exposed through the principal's typed accessors.

Discovery, cache, and rotation

Initialization is synchronous and fails closed unless both valid provider metadata and a valid non-empty JWKS are available. Redirects are disabled. Response bodies are bounded after transport decompression, HTTP requests have a client timeout, and the initialization context has its own deadline.

Metadata and keys share one synchronized refresh. Once the cached freshness deadline is reached, one admitted caller re-discovers metadata and conditionally fetches the current JWKS; other admitted callers wait with their own contexts. Provider cache headers are clamped to the configured refresh bounds. Positive freshness windows are refreshed early with per-instance jitter to spread fleet load. After the minimum refresh interval, an unknown key ID triggers one synchronized rotation probe even while known keys remain fresh. A rotated jwks_uri clears validators associated with the former URL.

Key material removed by a successful refresh is retired for the lifetime of the validator and cannot be reintroduced by a later provider rollback. This history is process-local and bounded; reconstructing the validator resets it.

Known keys remain usable only while the cache is fresh. A provider outage after expiry fails closed, including for a formerly known key. The failed refresh is cached until the minimum refresh interval, preventing a local retry storm; no provider response text is surfaced. The module starts no goroutines and owns no closeable lifetime beyond each synchronous call.

Concurrency, cancellation, and errors

Validator is immutable after construction and safe for concurrent use. Construction and every validation operation accept context.Context. Cancellation interrupts discovery, JWKS retrieval, refresh ownership, refresh waiting, and nonce validation where the collaborator honors the context.

Errors use the parent authentication package's stable classifications: malformed or missing credentials are invalid, cryptographic or claim failures are rejected, and provider/refresh/cancellation failures are unavailable. Errors never include tokens, claims, nonce values, keys, credentials, or arbitrary provider bodies. Applications must preserve that property in their own logs and callbacks.

Successful discovery and JWKS representations must use application/json or application/jwk-set+json; representation-free 304 Not Modified responses need no media type. Standard *http.Transport values are cloned with a 64 KiB response-header parsing ceiling. Returned headers and decompressed bodies are also bounded, and rejected bodies are closed. Custom transports must impose their own pre-allocation header ceiling. HTTPS JWKS endpoints may be cross-origin as allowed by Discovery; deployments that do not trust provider-directed egress must enforce origin, DNS, address, and port policy in the supplied HTTPClient transport.

The complete requirement and evidence matrices are in docs/hardening.md. The stable interpretation and defensive-policy register is in docs/specification-decisions.md.

Adoption and compatibility

The module follows stable v1 compatibility. Pin a reviewed version, configure an explicit algorithm set, enable nonce consumption for browser flows, and exercise provider metadata and key rotation before rollout. NewWithKeySet supports callers that already own standards-compliant key retrieval; those callers also own all key-cache, rotation, outage, and transport policy.

When migrating from a permissive verifier, review exact issuer spelling, additional audiences, azp, required iat, provider-advertised algorithms, HTTPS endpoints, and nonce behavior. Tokens previously accepted through issuer aliases, untrusted extra audiences, stale keys, or omitted nonce checks may be rejected intentionally.

FAQ

Does this start an authorization request or exchange a code? No. Supply the resulting ID token and, when available, its access token and authorization code.

Does this validate an access token? No. An access token is accepted only as an opaque input for at_hash binding.

Can stale keys be allowed during an outage? Only until their cache freshness deadline. There is no serve-stale override.

Can HTTP be enabled for one production endpoint? No. InsecureHTTP applies to the issuer and discovered endpoints and is intended for isolated development.

Who deletes replayed nonces? The caller's NonceValidator, atomically with successful validation.

Ecosystem

Use the Golib documentation portal to choose companion packages, supported stacks, recipes, and operations guidance.

Documentation

Overview

Package oidc provides strict OpenID Connect ID-token authentication using coreos/go-oidc and go-jose.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Clock deprecated

type Clock interface {
	clockpkg.Clock
}

Clock supplies validation time and permits deterministic tests.

Deprecated: depend on clock.Clock in new code. This named compatibility contract remains available throughout v1.

type Config

type Config struct {
	Issuer           string
	ClientID         string
	TrustedAudiences []string
	Algorithms       []string
	Clock            Clock
	ClockSkew        time.Duration
	NonceValidator   NonceValidator
	MaxTokenBytes    int
	MaxClaims        int
	MaxClaimDepth    int
	ScopeClaim       string
	TenantClaim      string
	InsecureHTTP     bool

	HTTPClient         *http.Client
	MaxHTTPBodyBytes   int64
	DiscoveryTimeout   time.Duration
	MaxKeys            int
	MinRefreshInterval time.Duration
	MaxRefreshInterval time.Duration
	MaxRefreshWaiters  int
}

Config defines a strict OIDC ID-token trust boundary.

type NonceValidator

type NonceValidator interface {
	ValidateNonce(context.Context, string) error
}

NonceValidator validates the per-authentication-flow OIDC nonce.

type NonceValidatorFunc

type NonceValidatorFunc func(context.Context, string) error

NonceValidatorFunc adapts a function to NonceValidator.

func (NonceValidatorFunc) ValidateNonce

func (f NonceValidatorFunc) ValidateNonce(ctx context.Context, nonce string) error

ValidateNonce calls f.

type TokenBinding

type TokenBinding struct {
	AccessToken       string
	AuthorizationCode string
}

TokenBinding supplies front-channel values that an ID token cryptographically binds through at_hash or c_hash. Values are used only during the call.

type Validator

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

Validator authenticates signed OIDC ID-token bearer credentials.

func New

func New(ctx context.Context, configuration Config) (*Validator, error)

New discovers an OIDC provider and creates a synchronous, bounded key-set validator. It starts no background goroutines.

func NewWithKeySet

func NewWithKeySet(configuration Config, keySet upstreamoidc.KeySet) (*Validator, error)

NewWithKeySet creates a validator from an upstream standards-compliant key set.

Example
private := externalRSAFixture.keys[0]
keys := &upstreamoidc.StaticKeySet{PublicKeys: []crypto.PublicKey{&private.PublicKey}}
now := time.Unix(1_800_000_000, 0).UTC()
validator, _ := authoidc.NewWithKeySet(authoidc.Config{
	Issuer: "https://issuer.example.test", ClientID: "client",
	Algorithms: []string{"RS256"}, Clock: authtest.NewClock(now),
}, keys)
signer, _ := jose.NewSigner(
	jose.SigningKey{Algorithm: jose.RS256, Key: private},
	(&jose.SignerOptions{}).WithType("JWT").WithHeader("kid", "key"),
)
payload, _ := json.Marshal(map[string]any{
	"sub": "user", "iss": "https://issuer.example.test", "aud": "client",
	"iat": now.Unix(), "exp": now.Add(time.Hour).Unix(),
})
signed, _ := signer.Sign(payload)
compact, _ := signed.CompactSerialize()
principal, err := validator.ValidateBearer(context.Background(), compact)
fmt.Println(err, principal.Subject())
Output:
<nil> user

func (*Validator) Authenticate

func (v *Validator) Authenticate(ctx context.Context, credential authentication.Credential) (authentication.Result, error)

Authenticate validates an OIDC bearer credential and returns an immutable principal.

func (*Validator) ValidateBearer

func (v *Validator) ValidateBearer(ctx context.Context, rawToken string) (authentication.Principal, error)

ValidateBearer verifies a bounded OIDC ID token.

func (*Validator) ValidateIDToken

func (v *Validator) ValidateIDToken(
	ctx context.Context,
	rawToken string,
	binding TokenBinding,
) (authentication.Principal, error)

ValidateIDToken verifies an ID token and any supplied access-token or authorization-code hash bindings.

Jump to

Keyboard shortcuts

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