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 ¶
- func Factory(delegationLifespan time.Duration, trustedIssuers []TrustedIssuer, ...) (server.Factory, error)
- func FactoryWithSharedTrustedIssuerValidator(delegationLifespan time.Duration, trustedIssuers []TrustedIssuer, ...) (server.Factory, error)
- func JWTBearerIssuanceFactory(trustedIssuers []TrustedIssuer, shared *MultiIssuerTokenValidator) (server.Factory, error)
- func ValidateJWKSURL(jwksURL string, insecureAllowHTTP, allowPrivateIPs bool) error
- func ValidateTrustedIssuers(trustedIssuers []TrustedIssuer, selfIssuer string, allowedAudiences []string) error
- type Handler
- func (*Handler) CanHandleTokenEndpointRequest(_ context.Context, requester fosite.AccessRequester) bool
- func (*Handler) CanSkipClientAuth(_ context.Context, _ fosite.AccessRequester) bool
- func (h *Handler) HandleTokenEndpointRequest(ctx context.Context, requester fosite.AccessRequester) error
- func (h *Handler) PopulateTokenEndpointResponse(ctx context.Context, requester fosite.AccessRequester, ...) error
- type JWTBearerAssertionValidator
- type JWTBearerGrantPolicy
- type JWTBearerHandler
- func (*JWTBearerHandler) CanHandleTokenEndpointRequest(_ context.Context, requester fosite.AccessRequester) bool
- func (*JWTBearerHandler) CanSkipClientAuth(ctx context.Context, requester fosite.AccessRequester) bool
- func (h *JWTBearerHandler) HandleTokenEndpointRequest(ctx context.Context, requester fosite.AccessRequester) error
- func (h *JWTBearerHandler) PopulateTokenEndpointResponse(ctx context.Context, requester fosite.AccessRequester, ...) error
- type JWTBearerSubjectBinding
- type MayActClaim
- type MultiIssuerTokenValidator
- type SelfIssuedTokenValidator
- type SubjectTokenValidator
- type TrustedIssuer
- type ValidatedClaims
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.
When trustedIssuers is non-empty, subject tokens are validated by a MultiIssuerTokenValidator wrapping the self-issued validator; otherwise the self-issued validator is used directly, preserving prior behavior exactly. Each TrustedIssuer carries its own InsecureAllowHTTP/AllowPrivateIPs (see NewMultiIssuerTokenValidator) — this Factory takes no validator-wide equivalent, so a self-issuer setting can never reach the external path through here.
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 an optional shared external-issuer validator. When shared is non-nil, it is used instead of building a second MultiIssuerTokenValidator. Callers enabling both the RFC 8693 token-exchange and RFC 7523 JWT-bearer grants for the same trusted issuers can share the validator created by NewSharedTrustedIssuerValidator.
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, when non-nil, is used as the JWTBearerAssertionValidator instead of building a second MultiIssuerTokenValidator: 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, so building one from each factory would double that cost for no benefit. Pass nil to build one locally (e.g. when only the JWT-bearer grant is enabled).
func ValidateJWKSURL ¶ added in v0.42.1
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 the single implementation shared by the runtime choke point above (ensureRegistered, on every fetch) and pkg/authserver/config.go's config-time check (validateJWKSEndpointURL): the two must not drift out of sync, or a laxer runtime check would silently defeat the config-time guard.
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 ¶
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, 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 a caller should pass to both Factory and JWTBearerIssuanceFactory when enabling both the RFC 8693 token-exchange and RFC 7523 JWT-bearer grants for the same trusted issuers, so only one JWKS cache/goroutine set per issuer is ever registered. Returns (nil, nil) when trustedIssuers is empty — both factories fall back to building their own validator (or none) in that case.
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 {
// 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.
// 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"`
// 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.
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.