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 ¶
- Variables
- func DefaultSkipPaths() map[string]bool
- func GenerateToken() (string, error)
- func LoadToken(agentRoot string) (string, error)
- func Middleware(opts MiddlewareOptions) func(http.Handler) http.Handler
- func Register(typeName string, factory Factory)
- func RegisteredTypes() []string
- func StoreToken(agentRoot, token string) error
- func TokenKind(token string) string
- func TokenPath(agentRoot string) string
- func UnmarshalSettings(in map[string]any, out any) error
- func ValidateToken(presented, expected string) bool
- func WithIdentity(ctx context.Context, id *Identity) context.Context
- type ChainProvider
- type Factory
- type Headers
- type Identity
- type MiddlewareOptions
- type Provider
Constants ¶
This section is empty.
Variables ¶
var ( ErrTokenNotForMe = errors.New("auth: token not for this provider") ErrTokenRejected = errors.New("auth: token rejected") ErrInvalidToken = errors.New("auth: invalid token") 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().
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 ¶
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 GenerateToken ¶
GenerateToken creates a cryptographically random bearer token. Returns a URL-safe base64-encoded string with 256 bits of entropy.
func LoadToken ¶
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 ¶
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 ¶
StoreToken writes a token to <agentRoot>/.forge/runtime.token with 0600 permissions.
func TokenKind ¶
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 UnmarshalSettings ¶
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 ¶
ValidateToken compares a presented token against the expected token using constant-time comparison to prevent timing attacks.
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) Providers ¶
func (c *ChainProvider) Providers() []Provider
Providers returns a defensive copy of the configured providers, in order.
type Factory ¶
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 ¶
Headers is a case-insensitive view over selected request headers passed to providers. Providers should not assume any particular casing.
func HeadersFromRequest ¶
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.
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"`
}
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 ¶
IdentityFromContext returns the Identity stored on ctx by WithIdentity, or nil if no Identity is present.
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.
Source Files
¶
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. |