auth

package
v0.19.0 Latest Latest
Warning

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

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

Documentation

Overview

Package auth provides bearer-token authentication for the A2A server, built around a pluggable Provider chain.

Each Provider claims tokens it recognizes and rejects the rest with ErrTokenNotForMe, allowing a ChainProvider to compose multiple providers in a first-match-wins fashion. The error return is the only signal for the verification outcome — there is intentionally no Identity.Valid field, because a nil-error return is the contract for "this token is valid."

New providers live under forge-core/auth/providers/<name>/ and register themselves via init() against the package-level registry, mirroring the database/sql driver pattern.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrTokenNotForMe         = errors.New("auth: token not for this provider")
	ErrTokenRejected         = errors.New("auth: token rejected")
	ErrInvalidToken          = errors.New("auth: invalid token")
	ErrProviderUnavailable   = errors.New("auth: provider unavailable")
	ErrProviderNotConfigured = errors.New("auth: provider not configured")
)

Sentinel errors that Providers and the ChainProvider use to signal outcomes.

  • ErrTokenNotForMe → provider does not recognize this token shape; ChainProvider should try the next provider.
  • ErrTokenRejected → provider recognized the token and denied it; ChainProvider stops and the middleware writes 401.
  • ErrInvalidToken → token is malformed or cryptographically invalid; ChainProvider stops and the middleware writes 401.
  • ErrProviderUnavailable → the verifier / IdP is unreachable or returned a transport-layer error (5xx, network timeout, garbage response). The token MAY be valid — we just can't say. ChainProvider stops (fail-closed, same as ErrInvalidToken), but the audit signal is distinct so operators don't chase a token issue when the actual problem is provider downtime.
  • ErrProviderNotConfigured → returned by New(); never by Verify().
View Source
var ErrMissingBearer = errorString("auth: missing bearer token")

ErrMissingBearer is returned (via OnAuth) when the request lacked an Authorization: Bearer ... header. Distinct from chain-level errors so callers can emit a precise "missing_token" reason code without parsing error strings.

Functions

func DefaultSkipPaths

func DefaultSkipPaths() map[string]bool

DefaultSkipPaths returns the default set of public endpoints that do not require authentication (agent card, health checks).

Both Agent Card paths are public:

  • /.well-known/agent-card.json — A2A 0.3.0 canonical path
  • /.well-known/agent.json — legacy alias (deprecated header emitted by the handler); removable after one release cycle

func FailReason added in v0.16.0

func FailReason(err error) string

FailReason maps a chain error to a stable, low-cardinality reason code suitable for span attributes, audit fields, dashboards, and alerting. Reason strings are part of the audit-event contract and the auth.verify span-attribute contract — changing them is a breaking change for downstream consumers.

Reason codes:

missing_token        - no Authorization header (or the auth header
                       the chain expected)
rejected             - provider recognized + denied (revoked,
                       expired, 401, 4xx)
invalid              - token malformed or cryptographically invalid
not_for_me           - chain exhausted, no provider claimed the
                       token
provider_unavailable - verifier/IdP unreachable (5xx, network,
                       undecodable)
infrastructure       - other unexpected error

provider_unavailable lets operators distinguish "the token is bad" alerts from "the IdP is down" alerts in their dashboards — the response and the runbook are different.

Lives in forge-core/auth so both the middleware's auth.verify span (issue #187) AND the audit-emit site in forge-cli/runtime use the same vocabulary. Single source of truth.

func GenerateToken

func GenerateToken() (string, error)

GenerateToken creates a cryptographically random bearer token. Returns a URL-safe base64-encoded string with 256 bits of entropy.

func LoadToken

func LoadToken(agentRoot string) (string, error)

LoadToken reads the stored token from <agentRoot>/.forge/runtime.token. Returns ("", nil) if the file does not exist.

func Middleware

func Middleware(opts MiddlewareOptions) func(http.Handler) http.Handler

Middleware returns an http.Handler that enforces bearer token authentication via the provided Provider chain.

Panics at construction if opts.Chain is nil and opts.AllowAnonymous is false. This is intentional — silently passing through requests when the caller forgot to wire a chain is the highest-impact misconfiguration in the auth subsystem (open prod endpoint). Fail-loud catches it at startup, not at the first request from a real user.

func Register

func Register(typeName string, factory Factory)

Register adds a provider factory under the given type name. Intended to be called from package init() in each provider subpackage. Panics on duplicate registration — a duplicate is a programming error that must fail loud at startup, not at the first request.

func RegisteredTypes

func RegisteredTypes() []string

RegisteredTypes returns a sorted, deduplicated slice of registered provider type names. Used by config validation and the wizard meta endpoint to expose the set of available provider types.

func StoreToken

func StoreToken(agentRoot, token string) error

StoreToken writes a token to <agentRoot>/.forge/runtime.token with 0600 permissions.

func TokenKind

func TokenKind(token string) string

TokenKind classifies a presented bearer token structurally — useful for audit logging without leaking the token itself.

"empty" → empty token "sigv4" → forge-aws-v1.<base64-url> (AWS Sigv4 via pre-signed URL pattern;

the magic prefix mirrors aws-iam-authenticator's "k8s-aws-v1.")

"jwt" → three base64url segments separated by dots "opaque" → anything else (custom verifier tokens, dev secrets, etc.)

This is a CHEAP structural check — it does not parse or validate. Never log the token; this helper is safe to log.

func TokenPath

func TokenPath(agentRoot string) string

TokenPath returns the path to the token file for the given agent root directory.

func UnmarshalSettings

func UnmarshalSettings(in map[string]any, out any) error

UnmarshalSettings decodes a freeform settings map into a typed Config struct, honoring `yaml:"..."` tags on the destination. Implemented as a yaml.Marshal + yaml.Unmarshal roundtrip so each provider can keep its Config strongly typed while the public schema stays map[string]any.

Pass a pointer to your Config struct as `out`.

func ValidateToken

func ValidateToken(presented, expected string) bool

ValidateToken compares a presented token against the expected token using constant-time comparison to prevent timing attacks.

func WithIdentity

func WithIdentity(ctx context.Context, id *Identity) context.Context

WithIdentity returns a copy of ctx that carries the given Identity. Storing nil is a no-op (returns ctx unchanged).

Types

type ChainProvider

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

ChainProvider composes multiple Providers into a single Provider, calling them in order and returning the first non-yielding result.

Behavior:

  • A Provider returning (id, nil) wins; later providers are not consulted.
  • A Provider returning ErrTokenNotForMe is skipped; the chain advances.
  • Any other error stops the chain immediately (fail-closed). The middleware surfaces this as 401. Falling through on non-yield errors would let an attacker bypass a temporarily-misbehaving provider.

A ChainProvider is immutable after construction and safe for concurrent use.

func NewChainProvider

func NewChainProvider(providers ...Provider) *ChainProvider

NewChainProvider returns a chain that calls the given providers in order. A chain with zero providers verifies nothing — Verify returns ErrTokenNotForMe.

func PrependChain

func PrependChain(chain Provider, prepend ...Provider) *ChainProvider

PrependChain returns a new ChainProvider whose providers are `prepend` followed by the providers of `chain`. If chain is a *ChainProvider, its providers are flattened (no nested chains). If chain is a non-chain Provider, it is appended as a single element. If chain is nil, the result contains only the prepended providers.

Useful for the runner to inject a loopback static_token at the chain head without callers having to know whether their chain already exists.

func (*ChainProvider) Name

func (c *ChainProvider) Name() string

Name implements Provider.

func (*ChainProvider) Providers

func (c *ChainProvider) Providers() []Provider

Providers returns a defensive copy of the configured providers, in order.

func (*ChainProvider) Verify

func (c *ChainProvider) Verify(ctx context.Context, token string, headers Headers) (*Identity, error)

Verify implements Provider with first-match-wins semantics.

type Factory

type Factory func(settings map[string]any) (Provider, error)

Factory constructs a Provider from a freeform settings map (typically the `settings` block of an `auth.providers[]` entry in forge.yaml).

A Factory should:

  • Unmarshal settings into its typed Config via UnmarshalSettings.
  • Call Config.Validate() and return clear errors.
  • Return ErrProviderNotConfigured for missing required fields.

A Factory must not perform network I/O — Verify() does that lazily.

type Headers

type Headers map[string]string

Headers is a case-insensitive view over selected request headers passed to providers. Providers should not assume any particular casing.

func HeadersFromRequest

func HeadersFromRequest(r *http.Request) Headers

HeadersFromRequest extracts the well-known headers providers may use. Keep this list narrow — providers should be explicit about the contract.

X-Goog-Iap-Jwt-Assertion is included for gcp_iap, which doesn't use a Bearer token. All other Phase 2 providers (aws_sigv4 with the pre-signed URL pattern, azure_ad) ride the standard Bearer path and don't need extra header surface here.

func (Headers) Get

func (h Headers) Get(key string) string

Get returns the value for the given header, matched case-insensitively.

type Identity

type Identity struct {
	UserID      string   `json:"user_id,omitempty"`
	Email       string   `json:"email,omitempty"`
	OrgID       string   `json:"org_id,omitempty"`
	WorkspaceID string   `json:"workspace_id,omitempty"`
	Groups      []string `json:"groups,omitempty"`

	// Claims carries the provider-specific raw payload (typically the
	// full JWT claim set for the oidc provider — including custom
	// issuer-specific claims). Treat this as an escape hatch for
	// provider-specific authorization logic; prefer the typed fields
	// above for portable consumers.
	//
	// WARNING (review #11f): for OIDC the map is an unfiltered shallow
	// copy of the JWT claims — `sub`, `email`, `iss`, `aud`, `exp`,
	// plus any custom claims the issuer adds (group memberships,
	// internal IDs, profile fields, sometimes raw PII). Do NOT log
	// this map verbatim. Filtering belongs in a future authz layer,
	// not here.
	Claims map[string]any `json:"claims,omitempty"`

	// Source records which provider verified the identity (e.g., "oidc",
	// "http_verifier", "static_token"). Useful for audit logs and debugging.
	Source string `json:"source,omitempty"`
	// contains filtered or unexported fields
}

Identity is the authenticated principal extracted by a Provider.

There is intentionally no Valid field — a non-nil *Identity returned alongside a nil error is the only "valid" signal. See package comment.

func IdentityFromContext

func IdentityFromContext(ctx context.Context) *Identity

IdentityFromContext returns the Identity stored on ctx by WithIdentity, or nil if no Identity is present.

func MarkRuntimeInternal added in v0.18.1

func MarkRuntimeInternal(id Identity) Identity

MarkRuntimeInternal returns a copy of id marked as minted by the runtime's own in-process loopback provider. The marker is unexported — reachable only through this constructor, never from YAML/JSON — and is what the channel on-behalf-of graft trusts (see applyChannelOnBehalfOf). Call it ONLY where the runtime mints its per-process loopback identity.

func (*Identity) IsRuntimeInternal added in v0.18.1

func (id *Identity) IsRuntimeInternal() bool

IsRuntimeInternal reports whether id was minted by the runtime's in-process loopback provider (via MarkRuntimeInternal).

type MiddlewareOptions

type MiddlewareOptions struct {
	// Chain is the provider chain that verifies bearer tokens. May only
	// be nil when AllowAnonymous is true (see below).
	Chain Provider

	// AllowAnonymous explicitly opts the middleware into running without
	// authentication. Required whenever Chain is nil — otherwise
	// Middleware() panics at construction. This prevents a misconfigured
	// runner from silently serving unauthenticated requests because
	// someone forgot to wire a chain.
	//
	// Set this to true when:
	//   - --no-auth flag is in effect (operator explicitly chose anon)
	//   - No auth: block AND no --auth-url AND no channels (legacy local
	//     dev default — preserved for backward compat)
	//
	// Leave this false for any production deployment that intends to
	// enforce auth; a nil chain will then panic loudly at startup
	// instead of running open.
	AllowAnonymous bool

	// SkipPaths maps "METHOD /path" keys that bypass authentication.
	// If nil, DefaultSkipPaths() is used.
	SkipPaths map[string]bool

	// OnAuth is an optional callback invoked on every auth decision.
	//
	//   - identity is non-nil and err is nil on success.
	//   - identity is nil and err carries the chain error on failure
	//     (or auth.ErrMissingBearer when the header was absent).
	//   - tokenKind is "jwt", "opaque", "sigv4", "iap_jwt", or "empty" —
	//     structural metadata safe to log. The token itself is NOT
	//     passed; callers must not try to recover it from the request.
	//
	// Callbacks should be cheap — they run on the request hot path.
	OnAuth func(r *http.Request, identity *Identity, err error, tokenKind string)
}

MiddlewareOptions configures Middleware.

type Provider

type Provider interface {
	Name() string
	Verify(ctx context.Context, token string, headers Headers) (*Identity, error)
}

Provider verifies a bearer token and returns the caller's Identity.

Implementations must:

  • Return (id, nil) on a verified token.
  • Return (nil, ErrTokenNotForMe) when the token is not for this provider (so the ChainProvider can try the next provider).
  • Return (nil, ErrTokenRejected) when the token is recognized but denied (e.g., revoked, expired, untrusted issuer).
  • Return (nil, ErrInvalidToken) when the token is malformed or cryptographically invalid.
  • Return (nil, other-error) for transient failures (network, etc.). The ChainProvider treats these as fatal (fail-closed) — it does NOT fall through to the next provider on infrastructure errors, because doing so would allow attackers to evade a temporarily-down provider.

func Build

func Build(typeName string, settings map[string]any) (Provider, error)

Build constructs a Provider for the given type name using the registered factory. Returns an error if the type is not registered.

Directories

Path Synopsis
providers
aws_sigv4
Package aws_sigv4 authenticates AWS-IAM callers using the pre-signed URL pattern.
Package aws_sigv4 authenticates AWS-IAM callers using the pre-signed URL pattern.
azure_ad
Package azure_ad authenticates Microsoft Entra ID (Azure AD) tokens.
Package azure_ad authenticates Microsoft Entra ID (Azure AD) tokens.
gcp_iap
Package gcp_iap authenticates requests that come through GCP's Identity-Aware Proxy.
Package gcp_iap authenticates requests that come through GCP's Identity-Aware Proxy.
httpverifier
Package httpverifier implements the legacy external auth provider: POST a JSON envelope to a verifier URL and trust its response.
Package httpverifier implements the legacy external auth provider: POST a JSON envelope to a verifier URL and trust its response.
oidc
Package oidc implements an auth.Provider that verifies JWT bearer tokens against an OpenID Connect issuer.
Package oidc implements an auth.Provider that verifies JWT bearer tokens against an OpenID Connect issuer.
statictoken
Package statictoken implements a Provider that matches the presented bearer token against a single expected value using constant-time comparison.
Package statictoken implements a Provider that matches the presented bearer token against a single expected value using constant-time comparison.

Jump to

Keyboard shortcuts

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