tokenexchange

package
v0.47.0 Latest Latest
Warning

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

Go to latest
Published: Sep 8, 2026 License: Apache-2.0 Imports: 30 Imported by: 0

Documentation

Overview

Package tokenexchange implements RFC 8693 token exchange and RFC 7523 JWT-bearer grants for the authorization server. It validates subject tokens issued by the same authorization server and trusted external JWT assertions, enabling delegated and assertion-based access-token issuance.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Factory

func Factory(
	delegationLifespan time.Duration, trustedIssuers []TrustedIssuer, configuredDelegateClients []string,
) (server.Factory, error)

Factory returns a server.Factory that creates a token exchange Handler. The delegationLifespan parameter sets the maximum lifetime for delegated tokens; the actual lifetime is the minimum of this value and the subject token's remaining lifetime. Returns an error if delegationLifespan is not in (0, server.MaxAccessTokenLifespan]: a zero or negative value would produce delegated tokens with an expiry already in the past, and a value above the access token ceiling would only be caught at request time by the per-request cap.

trustedIssuers must be empty here. A MultiIssuerTokenValidator owns per-issuer JWKS refresh worker pools that only its Close releases, and the bare Factory has no way to hand that instance back to the caller for shutdown (it is built at fosite-compose time, from config not available at this call). Passing a non-empty set therefore returns an error rather than silently building a validator whose workers leak; callers with trusted issuers must build it via NewSharedTrustedIssuerValidator, hold it for Close, and pass it to FactoryWithSharedTrustedIssuerValidator. With no trusted issuers the self-issued validator is used directly, preserving prior behavior exactly.

configuredDelegateClients is the operator-configured list of delegate client IDs (Config.DelegateClients, projected down to just their ClientIDs by the caller). An empty list preserves existing behavior exactly. The trust source here is server config, not client storage: the set is read once at process construction, so removing a client from config revokes its trust on the next restart rather than requiring any explicit revocation step against storage.

func FactoryWithSharedTrustedIssuerValidator added in v0.45.0

func FactoryWithSharedTrustedIssuerValidator(
	delegationLifespan time.Duration, trustedIssuers []TrustedIssuer, configuredDelegateClients []string,
	shared *MultiIssuerTokenValidator,
) (server.Factory, error)

FactoryWithSharedTrustedIssuerValidator is Factory with a shared external-issuer validator. shared is used as the subject-token validator when non-nil; it is REQUIRED whenever trustedIssuers is non-empty (an error is returned otherwise), because a locally-built MultiIssuerTokenValidator's JWKS refresh workers would have no owner to Close them — see the error below. Callers build it once with NewSharedTrustedIssuerValidator, hold it for Close, and can reuse the same instance across the RFC 8693 token-exchange and RFC 7523 JWT-bearer grants.

func JWTBearerIssuanceFactory added in v0.45.0

func JWTBearerIssuanceFactory(trustedIssuers []TrustedIssuer, shared *MultiIssuerTokenValidator) (server.Factory, error)

JWTBearerIssuanceFactory builds the production RFC 7523 handler. It is only registered by composition when a trusted issuer opts into the grant.

shared is used as the JWTBearerAssertionValidator and is REQUIRED whenever trustedIssuers is non-empty (an error is returned otherwise). The RFC 8693 token-exchange Factory and this one are usually enabled for the same trusted issuers, and each MultiIssuerTokenValidator registers its own per-issuer jwk.Cache and background refresh goroutines; sharing one instance avoids doubling that cost, and — since the validator's JWKS workers are released only by its Close — keeps them owned by the caller rather than built and abandoned inside this compose-time closure. Build it once with NewSharedTrustedIssuerValidator and hold it for Close.

func ValidateJWKSURL added in v0.42.1

func ValidateJWKSURL(jwksURL string, insecureAllowHTTP, allowPrivateIPs bool) error

ValidateJWKSURL checks that jwksURL parses, has a host, uses HTTPS unless insecureAllowHTTP permits plain HTTP — and only exactly the "http" scheme, not any other non-https scheme such as "file" or "ftp" — and, when the host is an IP literal, is not a private or loopback address unless allowPrivateIPs permits that. Both flags come from the specific TrustedIssuer being fetched (see ensureRegistered), never from a validator-wide or self-issuer setting. This prevents SSRF attacks where a compromised discovery document — or a hand-configured jwks_url — points to internal services.

This is shared by endpoint-only config-time validation and the runtime fetch choke point. Both use the issuer's allowPrivateIPs policy for literal IP hosts; runtime additionally protects DNS resolution on every outbound fetch.

func ValidateTrustedIssuerURL added in v0.47.0

func ValidateTrustedIssuerURL(issuerURL string, insecureAllowHTTP bool) error

ValidateTrustedIssuerURL checks that issuerURL is a valid trusted external OIDC issuer identifier. Trusted issuers require HTTPS unless their own insecureAllowHTTP opt-in is set; unlike this server's issuer, localhost is not exempt. Query, fragment, and userinfo are forbidden, while a trailing slash is permitted for providers such as Microsoft Entra ID v1.

func ValidateTrustedIssuers added in v0.42.1

func ValidateTrustedIssuers(trustedIssuers []TrustedIssuer, selfIssuer string, allowedAudiences []string) error

ValidateTrustedIssuers runs every structural check NewMultiIssuerTokenValidator performs on trustedIssuers — required fields, self-issuer collision, duplicate issuers, ActorClaim reachability, and ActorMatcher compilation — without constructing a validator or any per-issuer HTTP client. Config validation calls this to fail before the live upstream DCR registration and storage creation that run between RunConfig.Validate and server construction; NewMultiIssuerTokenValidator repeats the same checks at server startup as defence in depth. Both route through validateTrustedIssuer, so the two can't drift out of sync.

Types

type Handler

type Handler struct {
	*oauth2.HandleHelper
	// contains filtered or unexported fields
}

Handler implements RFC 8693 token exchange for user-to-agent delegation.

When an authenticated OAuth client (the acting agent) presents a user's JWT as subject_token, the handler validates the token and issues a delegated JWT with sub=user and an act claim containing the client's identity, per RFC 8693 Section 4.1.

Subject tokens are intentionally reusable within their lifetime: per RFC 8693's security considerations, a token exchange does not invalidate the subject token, so the same subject token may be exchanged more than once. Replay is bounded by the delegated token's lifetime cap (min(subject-remaining, delegation)), not by single-use tracking; per-jti single-use enforcement is deferred to the broader M2M/sender-constrained- token effort.

func (*Handler) CanHandleTokenEndpointRequest

func (*Handler) CanHandleTokenEndpointRequest(_ context.Context, requester fosite.AccessRequester) bool

CanHandleTokenEndpointRequest returns true if the request's grant_type is the RFC 8693 token exchange grant type.

func (*Handler) CanSkipClientAuth

func (*Handler) CanSkipClientAuth(_ context.Context, _ fosite.AccessRequester) bool

CanSkipClientAuth always returns false: RFC 8693 token exchange requires client authentication, and other handlers decide their own requirements.

func (*Handler) HandleTokenEndpointRequest

func (h *Handler) HandleTokenEndpointRequest(ctx context.Context, requester fosite.AccessRequester) error

HandleTokenEndpointRequest validates the token exchange request parameters, verifies the subject token, and constructs a delegated session with the act claim.

The delegated token's lifetime is the minimum of the subject token's remaining lifetime and the configured delegation lifespan.

func (*Handler) PopulateTokenEndpointResponse

func (h *Handler) PopulateTokenEndpointResponse(
	ctx context.Context, requester fosite.AccessRequester, responder fosite.AccessResponder,
) error

PopulateTokenEndpointResponse issues the delegated access token and sets the RFC 8693 issued_token_type in the response.

type JWTBearerAssertionValidator added in v0.45.0

type JWTBearerAssertionValidator interface {
	ValidateJWTBearerAssertion(ctx context.Context, rawToken, tokenEndpoint string) (*ValidatedClaims, error)
}

JWTBearerAssertionValidator verifies cryptographic and registered claims of a plain RFC 7523 JWT-bearer assertion.

type JWTBearerGrantPolicy added in v0.45.0

type JWTBearerGrantPolicy struct {
	MaxAssertionAge string                    `json:"max_assertion_age" yaml:"max_assertion_age"`
	SubjectBindings []JWTBearerSubjectBinding `json:"subject_bindings" yaml:"subject_bindings"`
	// AcceptedAudiences is the set of "this AS" identity strings an
	// assertion's "aud" claim must intersect — e.g. to support migrating
	// this server's issuer/token-endpoint URL, or exposing it under more
	// than one valid name. Each value uniquely identifies this
	// authorization server for this grant; it is NOT a resource/API
	// identifier — a bare resource audience is deliberately not accepted
	// here, that would let any RFC 8707 resource-scoped token satisfy the
	// grant instead of only tokens minted for this AS. Defaults to
	// [tokenEndpoint] when empty, preserving prior exact-match behavior.
	AcceptedAudiences []string `json:"accepted_audiences,omitempty" yaml:"accepted_audiences,omitempty"`
	// contains filtered or unexported fields
}

JWTBearerGrantPolicy enables RFC 7523 JWT-bearer assertions for an issuer. MaxAssertionAge uses Go's duration syntax in serialized RunConfig.

type JWTBearerHandler added in v0.45.0

type JWTBearerHandler struct {
	*oauth2.HandleHelper
	// contains filtered or unexported fields
}

JWTBearerHandler implements the unbound RFC 7523 JWT-bearer grant.

func (*JWTBearerHandler) CanHandleTokenEndpointRequest added in v0.45.0

func (*JWTBearerHandler) CanHandleTokenEndpointRequest(_ context.Context, requester fosite.AccessRequester) bool

CanHandleTokenEndpointRequest only claims plain assertions. A recognized ID-JAG assertion is intentionally left for a future bound handler; malformed and unsupported typ values remain this handler's responsibility to reject.

func (*JWTBearerHandler) CanSkipClientAuth added in v0.45.0

func (*JWTBearerHandler) CanSkipClientAuth(ctx context.Context, requester fosite.AccessRequester) bool

CanSkipClientAuth permits a credential-free plain JWT-bearer assertion. fosite.NewAccessRequest always calls AuthenticateClient first and only discards its error when this returns true (access_request_handler.go), and AuthenticateClient falls back to reading HTTP Basic credentials off the raw request when no form-based client_assertion is present (client_authentication.go). If this only inspected the form, a request carrying a valid assertion plus a wrong HTTP Basic password would be silently accepted, since the resulting clientErr would be discarded without ever being looked at. So the raw *http.Request that fosite stashes under RequestContextKey must be checked for Basic credentials too; treat a missing/malformed request as "credentials might be present" so this fails closed rather than open.

func (*JWTBearerHandler) HandleTokenEndpointRequest added in v0.45.0

func (h *JWTBearerHandler) HandleTokenEndpointRequest(ctx context.Context, requester fosite.AccessRequester) error

HandleTokenEndpointRequest validates policy and prepares a bounded access-token session.

func (*JWTBearerHandler) PopulateTokenEndpointResponse added in v0.45.0

func (h *JWTBearerHandler) PopulateTokenEndpointResponse(
	ctx context.Context, requester fosite.AccessRequester, responder fosite.AccessResponder,
) error

PopulateTokenEndpointResponse issues only an access token.

type JWTBearerSubjectBinding added in v0.45.0

type JWTBearerSubjectBinding struct {
	Subject          string   `json:"subject" yaml:"subject"`
	AllowedResources []string `json:"allowed_resources" yaml:"allowed_resources"`
}

JWTBearerSubjectBinding restricts a JWT-bearer assertion subject to the resources for which it may obtain an access token.

type MayActClaim

type MayActClaim struct {
	Sub string `json:"sub"`
	// Iss, when present, qualifies Sub's namespace. RFC 8693 §4.4: "the
	// combination of the two claims 'iss' and 'sub' are sometimes necessary
	// to uniquely identify an authorized actor." Without it, Sub alone could
	// name a party in some other issuer's namespace while still being
	// compared against a ToolHive client ID by checkDelegationConsent — a
	// namespace-confusion bug, not just a missing feature. validateMayActShape
	// requires Iss, when present, to equal this authorization server's own
	// issuer, so by the time checkDelegationConsent reads Sub it is
	// guaranteed to be in ToolHive's own client namespace.
	Iss string `json:"iss,omitempty"`
}

MayActClaim represents the RFC 8693 §4.4 may_act claim from a subject token. It identifies the party authorized to act on behalf of the subject.

type MultiIssuerTokenValidator added in v0.41.0

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

MultiIssuerTokenValidator validates subject tokens from the authorization server itself or from configured external OIDC issuers, delegating self-issued tokens to SelfIssuedTokenValidator and resolving external issuers' JWKS (via OIDC discovery if needed) to verify signature and claims.

A valid signature and audience alone would authorize ToolHive as a resource, not any particular client, as a delegate — a confused-deputy risk (CWE-863). validateExternalToken therefore requires a "may_act" claim or authorization by AllowedActors or ActorMatcher before returning successfully. See docs/arch/17-token-exchange-delegation.md for the full consent-signal precedence and trust model.

func NewMultiIssuerTokenValidator added in v0.41.0

func NewMultiIssuerTokenValidator(
	selfValidator *SelfIssuedTokenValidator,
	selfIssuer string,
	trustedIssuers []TrustedIssuer,
	allowedAudiences []string,
) (_ *MultiIssuerTokenValidator, retErr error)

NewMultiIssuerTokenValidator creates a validator that accepts tokens from the authorization server itself and from the provided trusted external issuers. Returns an error if selfValidator is nil, selfIssuer is empty, any TrustedIssuer is invalid (see validateTrustedIssuer), or an issuer's dedicated HTTP client cannot be built (see newExternalIssuerConfig for why each issuer gets its own).

func NewSharedTrustedIssuerValidator added in v0.45.0

func NewSharedTrustedIssuerValidator(
	config *server.AuthorizationServerConfig, trustedIssuers []TrustedIssuer,
) (*MultiIssuerTokenValidator, error)

NewSharedTrustedIssuerValidator builds the single MultiIssuerTokenValidator that backs a server's trusted-issuer handling. buildProvider constructs it whenever any trusted issuer is configured — not only when the RFC 7523 JWT-bearer grant is also enabled — and passes it to FactoryWithSharedTrustedIssuerValidator (and, when the JWT-bearer grant is enabled, JWTBearerIssuanceFactory), both of which now require it for a non-empty trusted-issuer set rather than building their own. One shared instance keeps a single JWKS cache/goroutine set per issuer and, crucially, gives the server one validator to Close on shutdown so those goroutines are released (see MultiIssuerTokenValidator.Close). Returns (nil, nil) when trustedIssuers is empty, in which case no external validator is built.

func (*MultiIssuerTokenValidator) Close added in v0.47.0

func (v *MultiIssuerTokenValidator) Close() error

Close shuts down every per-issuer jwk.Cache, stopping the background JWKS refresh worker pool (and its ~3 goroutines) each one runs. It cancels the validator-scoped context those pools share — signalling them all to stop at once — then waits, under a single shared httpTimeout budget, for each cache to drain. Cancelling up front (rather than after the loop) means the pools unwind in parallel, and the one shared deadline bounds the total wait by httpTimeout rather than httpTimeout×N even if a pool ignores cancellation. This is the same order the construction-failure path uses. It is safe to call more than once and on a validator with no external issuers; the validator must not be used after Close.

A server holds its MultiIssuerTokenValidator and calls this from Close and its construction error path (see pkg/authserver), so neither a normal shutdown nor a failed reconstruction leaks these workers. authserver.CloseIdleConnections deliberately does NOT reach it: that path must stay safe to call on a still-serving server, which needs the workers to keep refreshing external issuers' keys.

func (*MultiIssuerTokenValidator) Validate added in v0.41.0

func (v *MultiIssuerTokenValidator) Validate(ctx context.Context, rawToken string) (*ValidatedClaims, error)

Validate parses the raw JWT to extract the issuer claim, then routes validation to either the self-issued validator or the appropriate external issuer validator. Returns an error if the issuer is not trusted.

func (*MultiIssuerTokenValidator) ValidateJWTBearerAssertion added in v0.45.0

func (v *MultiIssuerTokenValidator) ValidateJWTBearerAssertion(
	ctx context.Context, rawToken, tokenEndpoint string,
) (*ValidatedClaims, error)

ValidateJWTBearerAssertion verifies a plain RFC 7523 assertion issued by an external issuer explicitly enabled for the JWT-bearer grant. It deliberately does not apply RFC 8693 delegation consent (AllowedActors, ActorMatcher, or may_act): a JWT-bearer assertion's authorization policy is separate from delegation policy.

The assertion audience must intersect the issuer's configured JWTBearerGrant.AcceptedAudiences, or just tokenEndpoint when that is unset. The caller is responsible for the phase-specific subject binding, maximum age, replay, and issuance policy after this cryptographic verification succeeds.

type SelfIssuedTokenValidator added in v0.41.0

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

SelfIssuedTokenValidator validates subject tokens presented during RFC 8693 token exchange. It verifies that the token was issued by this authorization server by checking the signature against the server's own JWKS, and validates standard JWT claims.

func NewSelfIssuedTokenValidator added in v0.41.0

func NewSelfIssuedTokenValidator(
	jwks *jose.JSONWebKeySet, issuer string, allowedAudiences []string,
) (*SelfIssuedTokenValidator, error)

NewSelfIssuedTokenValidator creates a new validator for subject tokens. The jwks parameter must be non-nil and contain only the authorization server's public signing keys (e.g. AuthorizationServerConfig.PublicJWKS) — the validator only ever verifies signatures, so it must not be handed private key material. The issuer parameter is the expected "iss" claim value. allowedAudiences is the set of audiences this server accepts in a subject token's "aud" claim; per the same secure default as AuthorizationServerConfig.AllowedAudiences, an empty allowedAudiences rejects every subject token rather than skipping the check.

func (*SelfIssuedTokenValidator) Validate added in v0.41.0

func (v *SelfIssuedTokenValidator) Validate(_ context.Context, rawToken string) (*ValidatedClaims, error)

Validate parses and verifies a raw JWT subject token. It checks the signature against the server's JWKS, validates issuer and audience, ensures the token is not expired, and requires a subject claim for delegation.

The subject token's "aud" claim is checked against allowedAudiences, but this validator deliberately does not require that the authorization server itself (i.e. the token endpoint) be among those audiences. RFC 8693 leaves subject-token validation criteria out of scope, and ToolHive's vMCP flow legitimately exchanges tokens addressed to a downstream/upstream resource rather than the AS. The residual cross-resource risk is mitigated elsewhere: the token is still pinned to the server-wide allowedAudiences, and the handler enforces the requested resource against the client's registered audiences.

Returns the validated claims on success, or a descriptive error on failure.

type SubjectTokenValidator

type SubjectTokenValidator interface {
	Validate(ctx context.Context, rawToken string) (*ValidatedClaims, error)
}

SubjectTokenValidator validates subject tokens presented during RFC 8693 token exchange.

type TrustedIssuer added in v0.41.0

type TrustedIssuer struct {
	// Name optionally identifies this trust declaration for canonical issuer_ref references.
	Name string `json:"name,omitempty" yaml:"name,omitempty"`
	// IssuerURL is the expected "iss" claim value (exact match).
	IssuerURL string `json:"issuer_url" yaml:"issuer_url"`
	// ExpectedAudience is the expected "aud" claim value that must appear
	// in an RFC 8693 subject token's audience list (a resource/API identifier,
	// not a client ID — required for delegation unless JWTBearerGrant is
	// configured; see looksLikeResourceIdentifier). RFC 7523 assertions use
	// the token endpoint as their audience instead.
	//
	// This legacy field is deprecated; configure RFC 8693 policy under
	// inbound_grants.token_exchange.issuer_policies.
	// See docs/arch/17-token-exchange-delegation.md ("ID/access-token
	// discrimination") for why and its limits.
	ExpectedAudience string `json:"expected_audience" yaml:"expected_audience"`
	// JWKSURL is the URL to fetch the issuer's JSON Web Key Set from.
	// If empty, it is resolved via OIDC discovery at {IssuerURL}/.well-known/openid-configuration.
	JWKSURL string `json:"jwks_url,omitempty" yaml:"jwks_url,omitempty"`
	// InsecureAllowHTTP permits plain-HTTP OIDC discovery and JWKS fetches
	// for THIS issuer only. Development and testing only — never set in
	// production. Does not relax the private-IP guard; see AllowPrivateIPs.
	// Deliberately per-issuer: this server's own InsecureAllowHTTP must not
	// silently permit plaintext discovery for every trusted external issuer
	// too — a network attacker who can intercept that traffic could
	// substitute a JWKS and forge subject tokens for that issuer's
	// namespace.
	InsecureAllowHTTP bool `json:"insecure_allow_http,omitempty" yaml:"insecure_allow_http,omitempty"`
	// AllowPrivateIPs permits OIDC discovery and JWKS fetches for THIS
	// issuer to resolve to a private or loopback address. Use only when the
	// issuer is hosted inside the same cluster and has no public endpoint.
	AllowPrivateIPs bool `json:"allow_private_ips,omitempty" yaml:"allow_private_ips,omitempty"`
	// CAFilePath is the path to a PEM CA bundle added to the system roots when
	// fetching this issuer's OIDC discovery document and JWKS. Trust is additive
	// and scoped to this issuer: the public roots still apply, and no other
	// issuer's client is affected.
	CAFilePath string `json:"ca_file_path,omitempty" yaml:"ca_file_path,omitempty"`
	// ActorClaim names the claim identifying the client that requested the
	// subject token from THIS EXTERNAL ISSUER (used by AllowedActors below).
	// Values are in the external issuer's namespace, NOT ToolHive client
	// IDs. Defaults to "azp"; use "appid" for Microsoft Entra v1, "cid" for
	// Okta. The special value "client_id" reads ValidatedClaims.ClientID
	// instead of Extra (assignClaim routes it to that field) — it is still
	// the external token's client_id claim, not a ToolHive one.
	ActorClaim string `json:"actor_claim,omitempty" yaml:"actor_claim,omitempty"`
	// AllowedActors is the allowlist of ActorClaim values authorized to
	// exchange a subject token from this issuer when it carries no
	// "may_act" claim. ActorMatcher can additionally authorize a token by
	// matching its complete verified claims map; either signal is sufficient.
	// When both are empty, only may_act-bearing tokens are accepted, and only
	// if AllowMayAct is also true for this issuer. By itself names no
	// ToolHive client — see AllowedDelegateClients and
	// docs/arch/17-token-exchange-delegation.md ("Accepted limitations" #1).
	AllowedActors []string `json:"allowed_actors,omitempty" yaml:"allowed_actors,omitempty"`
	// ActorMatcher is an admin-authored CEL expression evaluated against the
	// complete signature-verified JWT claims map as "claims". A true result
	// authorizes delegation alongside AllowedActors; a syntax or type error
	// fails configuration validation. An expression that compiles but does
	// not return bool is NOT caught at that point, though — it compiles
	// successfully and is only rejected the first time it is evaluated
	// against a real token, denying that token (and every one after it, since
	// the expression will never return bool). Any other runtime evaluation
	// error denies the token the same way.
	ActorMatcher string `json:"actor_matcher,omitempty" yaml:"actor_matcher,omitempty"`
	// AllowedDelegateClients restricts which ToolHive client IDs may
	// exchange a subject token from this issuer, for BOTH consent paths.
	// Required (validateTrustedIssuer rejects empty/absent); "*" permits
	// any confidential client holding the grant. See
	// docs/arch/17-token-exchange-delegation.md ("Accepted limitations" #1).
	//nolint:lll // field tags require full JSON+YAML names
	AllowedDelegateClients []string `json:"allowed_delegate_clients,omitempty" yaml:"allowed_delegate_clients,omitempty"`
	// AllowMayAct permits this external issuer's may_act claim to authorize
	// delegation. It defaults to false; external issuers must be opted in
	// explicitly because may_act bypasses AllowedActors and ActorMatcher. It
	// does not affect self-issued subject tokens. When enabled,
	// AllowedDelegateClients must name specific ToolHive clients rather than
	// use the wildcard.
	AllowMayAct bool `json:"allow_may_act,omitempty" yaml:"allow_may_act,omitempty"`
	// JWTBearerGrant optionally enables the plain RFC 7523 JWT-bearer grant.
	// It accepts assertions from this issuer without client authentication and
	// limits their maximum age, subjects, and RFC 8707 resources. It is
	// independent from RFC 8693 delegation policy.
	//
	// This legacy field is deprecated; configure RFC 7523 policy under
	// inbound_grants.jwt_bearer.issuer_policies.
	JWTBearerGrant *JWTBearerGrantPolicy `json:"jwt_bearer_grant,omitempty" yaml:"jwt_bearer_grant,omitempty"`
}

TrustedIssuer configures an external OIDC issuer whose tokens are accepted as RFC 8693 subject tokens or RFC 7523 JWT-bearer assertions.

This type is reused verbatim as the wire schema for authserver.RunConfig.TrustedIssuers (deliberately, to avoid a parallel type that drifts — see the go-style rule against that). Its JSON/YAML tags are therefore part of the serialized RunConfig, which is reflected into docs/server/swagger.*; adding, renaming, or retagging a field here is a schema change, not a purely internal one.

func ResolveJWTBearerGrantPolicies added in v0.45.0

func ResolveJWTBearerGrantPolicies(issuers []TrustedIssuer) ([]TrustedIssuer, error)

ResolveJWTBearerGrantPolicies clones grant policies and parses their duration fields once at the RunConfig-to-runtime boundary.

type ValidatedClaims

type ValidatedClaims struct {
	// Subject is the user identity from the "sub" claim (required for delegation).
	Subject string
	// Issuer is the token issuer from the "iss" claim.
	Issuer string
	// Audience is the list of intended recipients from the "aud" claim.
	Audience []string
	// Expiry is the token expiration time from the "exp" claim.
	Expiry time.Time
	// IssuedAt is the token issuance time from the "iat" claim.
	IssuedAt time.Time
	// JWTID is the unique token identifier from the "jti" claim.
	JWTID string
	// Name is the user's display name from the custom "name" claim.
	Name string
	// Email is the user's email address from the custom "email" claim.
	Email string
	// ClientID is the OAuth client ID from the custom "client_id" claim.
	ClientID string
	// Scopes is the space-delimited scope string assembled from the "scope"
	// or "scp" claim. RFC 9068 §2.2.1 spells it "scope" as a JSON string, but
	// fosite's default JWT claims strategy (token/jwt/claims_jwt.go) writes
	// scopes as a JSON array under "scp" instead, unless ScopeField is
	// explicitly set to String or Both — this server does not set it, so a
	// genuine ToolHive-issued access token used as a subject token carries
	// "scp", not "scope". When both are present, "scope" wins. Empty if the
	// subject token carries neither claim.
	Scopes string
	// MayAct holds the authorized actor from the "may_act" claim (RFC 8693 §4.4).
	// Nil when the subject token does not carry a may_act claim.
	MayAct *MayActClaim
	// ExternalActor is the external actor claim value when the allowlist
	// authorization path matched. It is empty when ActorMatcher alone
	// authorized the token, because a matcher need not identify an actor.
	// It is never populated from token claims by buildValidatedClaims or
	// assignClaim, and is empty for self-issued and may_act-bearing tokens.
	ExternalActor string
	// ExternalActorAuthorized reports that the external issuer authorized
	// delegation through AllowedActors or ActorMatcher. It is separate from
	// ExternalActor because ActorMatcher can authorize without an actor claim.
	// It is false for self-issued and may_act-bearing tokens.
	ExternalActorAuthorized bool
	// ExternalIssuer is set by the external-issuer validation path
	// (validateExternalToken in multi_issuer_validator.go) to that issuer's
	// IssuerURL, for EVERY external token it validates — unlike
	// ExternalActor, this is set regardless of whether the token carries a
	// may_act claim. It exists so the handler can record provenance (which
	// issuer a delegation actually originated from) even for a
	// may_act-bearing external token, which leaves ExternalActor unset.
	// Empty for self-issued tokens. Like ExternalActor, it is never
	// populated from token claims by buildValidatedClaims or assignClaim —
	// it comes from the already-validated issuer config the token matched,
	// not from anything token-supplied, so it cannot be spoofed via claims.
	ExternalIssuer string
	// AllowedDelegateClients is set for EVERY external token — like
	// ExternalIssuer and unlike ExternalActor, it does not depend on whether
	// the token carries a may_act claim (see validateExternalToken). That
	// matters: the may_act path bypasses AllowedActors and ActorMatcher
	// entirely, so it is the path that most needs this restriction to still
	// apply. It is set to that issuer's configured
	// TrustedIssuer.AllowedDelegateClients, and is never populated
	// from token claims by buildValidatedClaims or assignClaim, and the
	// validator never compares it against anything: the validator does not
	// know the authenticated ToolHive client, so checkDelegationConsent
	// (handler.go) is the one that checks actorID against this list. Nil
	// means the issuer did not configure AllowedDelegateClients, which is
	// permissive — any ToolHive client may use the allowlisted external
	// actor, same as before this field existed.
	AllowedDelegateClients []string
	// Extra contains all non-standard claims not captured by other fields.
	Extra map[string]any
}

ValidatedClaims holds the verified claims extracted from a subject token. All fields are populated from a successfully validated JWT that was issued by this authorization server.

Jump to

Keyboard shortcuts

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