Documentation
¶
Overview ¶
Package aauth implements the AAuth protocol (draft-hardt-oauth-aauth-protocol-11): agent identity and authorization across trust domains, without shared secrets or per-server pre-registration. Every agent gets its own signing key (Ed25519 by default, or ES256 — any crypto.Signer, so hardware-backed keys work) and a self-describing token that binds that key; any party can verify it.
Layers ¶
AAuth stacks these concerns, each usable independently:
- Identity — an agent proves who it is on every request. See Agent and Agent.MintToken for minting an aa-agent+jwt, SignRequest and VerifyAndExtractAgent for the RFC 9421 HTTP message-signature profile over the Signature-Key carrier (draft-hardt-httpbis-signature-key-09).
- Person identity — a Person Server identifies the person an agent acts for to one resource with a person token (draft -11 §7.1). See IssuePersonToken (PS side) and VerifyAndExtractPerson (resource side).
- Resource access — a protected API decides what an agent may do. See Transport (the agent-side client that turns challenges into person and auth token requests automatically), PSClient.PersonToken and PSClient.RequestAuthToken (three-party PS-asserted access), and IssueResourceToken / VerifyAndExtractAuth (the resource side).
- Delegation — call chaining through intermediaries (RouteDownstream, ChainRouter.Transport) and parent-mediated sub-agents with their own keys (Agent.NewSubAgent).
- Governance — optional missions, permission requests, audit, and the relay channel to the person. See PSClient.ProposeMission, PSClient.RequestPermission, PSClient.Audit, and PSClient.RequestInteraction.
- Revocation — servers revoke the tokens they issued at the holder's revocation endpoint (RevocationClient, draft -11 §11.12).
Deployment shapes ¶
The self-hosted / local-agent shape is a first-class target: the agent is its own agent provider (draft-hardt-aauth-bootstrap-02 §4.3), which is what autonomous coding agents on developer machines are. Verification trust is pluggable via KeyResolver: JWKSResolver for public discovery, StaticResolver for pinned keys (offline / air-gapped), and SelfSignedResolver for local agents.
Server roles ¶
The root package is the protocol vocabulary shared by every role. The hosted server roles are subpackages, each an http.Handler over caller-supplied storage and policy interfaces: personserver (Person Server), accessserver (Access Server, and PS-AS federation), and agentprovider (hosted agent provider). Each consults an optional Limiter; the ratelimit package provides an in-memory one for a single instance.
The drafts implemented are named by ProtocolDraft, SignatureKeyDraft, and BootstrapDraft.
This is, to our knowledge, the first Go implementation of the protocol.
Example ¶
Example shows the two ends of the cooperative permission flow: an agent asks a Person Server before acting, and the server authenticates the agent.
package main
import (
"context"
"fmt"
"log"
"net/http"
aauth "github.com/aauth-dev/aauth-go"
)
func main() {
// Agent side: mint an identity and ask before acting.
id, _ := aauth.ParseAgentIdentifier("aauth:claude-code@devbox.local")
agent, _ := aauth.NewAgent(id, aauth.WithPersonServer("http://127.0.0.1:7421"))
ps := aauth.NewPSClient("http://127.0.0.1:7421", agent)
ps.HTTPClient = http.DefaultClient // local development: the default client refuses http and loopback
_, err := ps.RequestPermission(context.Background(), aauth.PermissionRequest{
Action: "WriteFile",
Description: "write the deploy config",
Parameters: map[string]any{"path": "/tmp/deploy.yaml"},
})
_ = err // res.Granted() reports the decision; a 202 is followed automatically.
// Server side: authenticate the agent behind an endpoint.
_ = func(w http.ResponseWriter, r *http.Request) {
claims, err := aauth.VerifyAndExtractAgent(r.Context(), r, aauth.VerifyAgentTokenOptions{
Resolver: aauth.SelfSignedResolver{},
})
if err != nil {
// 401 with the matching Signature-Error code.
aauth.WriteSignatureFailure(w, err)
return
}
if _, err := fmt.Fprintf(w, "authenticated %s", claims.Subject); err != nil {
log.Printf("write response: %v", err)
}
}
}
Output:
Example (Resource) ¶
Example_resource is a resource using PS authorization (three-party): it answers an agent token with requirement=person-token, a verified person token with a resource token challenge (requirement=auth-token), and serves a request presenting a verified auth token.
package main
import (
"context"
"fmt"
"log"
"net/http"
"sync"
aauth "github.com/aauth-dev/aauth-go"
)
func main() {
const self = "https://files.example" // the resource's server identifier
key, err := aauth.GenerateKey(aauth.AlgEd25519)
if err != nil {
log.Fatal(err)
}
jwk, err := aauth.NewJWK(key.Public())
if err != nil {
log.Fatal(err)
}
// Person and auth token keys are discovered from each issuer's JWKS.
// Token issuers are URLs the request chooses, so a nil client uses
// aauth.DiscoveryClient, which reaches only public https destinations.
verify := aauth.TokenVerifyOptions{Resolver: aauth.NewJWKSResolver(nil)}
// A valid signature proves the issuer signed the claims, not that the
// subject may use this resource. The resource keeps its own record of
// the people it serves, keyed by (iss, sub): sub is unique only within
// its issuer, so records from different issuers never match
// (§9.4.3.2). This one enrolls people on first use, but only from the
// person and access servers it has chosen to trust.
trusted := map[string]bool{"https://ps.example": true, "https://as.example": true}
var mu sync.Mutex
people := map[[2]string]bool{}
checkSubject := func(_ context.Context, iss, sub string) error {
if !trusted[iss] {
return fmt.Errorf("%w: %s is not an issuer this resource accepts", aauth.ErrInvalidToken, iss)
}
mu.Lock()
defer mu.Unlock()
people[[2]string{iss, sub}] = true
return nil
}
handler := func(w http.ResponseWriter, r *http.Request) {
tok, err := aauth.ParseSignatureKey(r)
if err != nil {
aauth.WriteSignatureFailure(w, err)
return
}
typ, err := aauth.TokenType(tok)
if err != nil {
aauth.WriteSignatureFailure(w, err)
return
}
switch typ {
case aauth.TypAuth:
claims, err := aauth.VerifyAndExtractAuth(r.Context(), r, self, aauth.AuthTokenVerifyOptions{TokenVerifyOptions: verify, CheckSubject: checkSubject})
if err != nil {
aauth.WriteSignatureFailure(w, err)
return
}
if _, err := fmt.Fprintf(w, "hello %s (%s)", claims.Subject, claims.Scope); err != nil {
log.Printf("write response: %v", err)
}
case aauth.TypPerson:
person, err := aauth.VerifyAndExtractPerson(r.Context(), r, self, verify)
if err != nil {
aauth.WriteSignatureFailure(w, err)
return
}
// The resource token's aud defaults to the person's PS.
rt, err := aauth.IssueResourceToken(aauth.ResourceTokenParams{Resource: self, Scope: "files:read"}, person, key, jwk.Kid)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
aauth.ChallengeAuthToken(w, rt)
default:
aauth.ChallengePersonToken(w)
}
}
http.HandleFunc("/files", handler)
}
Output:
Index ¶
- Constants
- Variables
- func AcceptSignatureFromResponse(res *http.Response) (schemes, algs []string, err error)
- func AcceptedSignatureAlgs() []string
- func AcceptedSignatureSchemes() []string
- func AlgForPublicKey(pub crypto.PublicKey) (string, error)
- func AttachSignatureKey(req *http.Request, token string)
- func AttachSignatureKeyJWKSURI(req *http.Request, id, dwk, kid string)
- func BoundedExpiry(now time.Time, ttl, max time.Duration, bounds ...time.Time) (time.Time, error)
- func ChainInteraction(w http.ResponseWriter, pendingURL, interactionURL, code string)
- func ChallengeAgentToken(w http.ResponseWriter)
- func ChallengeAuthToken(w http.ResponseWriter, resourceToken string)
- func ChallengePersonToken(w http.ResponseWriter)
- func DiscoveryClient() *http.Client
- func DoDeferred(ctx context.Context, hc *http.Client, req *http.Request, opts DeferredOptions) (*http.Response, error)
- func EgressClient() *http.Client
- func FetchMetadata(ctx context.Context, hc *http.Client, base, doc string, dst any) error
- func FollowDeferred(ctx context.Context, hc *http.Client, reqURL *url.URL, res *http.Response, ...) (*http.Response, error)
- func GenerateKey(alg string) (crypto.Signer, error)
- func IssueResourceToken(p ResourceTokenParams, presented PresentedToken, key crypto.Signer, kid string) (string, error)
- func MintAgentToken(claims AgentClaims, key crypto.Signer, kid string) (string, error)
- func MintAuthToken(claims AuthClaims, key crypto.Signer, kid string) (string, error)
- func MintPersonToken(claims PersonClaims, key crypto.Signer, kid string) (string, error)
- func MintResourceToken(claims ResourceClaims, key crypto.Signer, kid string) (string, error)
- func MissionS256(blob []byte) string
- func ParseSignatureKey(req *http.Request) (string, error)
- func ResourceLinks(h http.Header) []string
- func ResourceMetadataLink(resource string) string
- func RevocationEndpoint(ctx context.Context, hc *http.Client, issuer, dwk string) (string, error)
- func SignRequest(req *http.Request, key crypto.Signer, keyid string) error
- func TokenType(token string) (string, error)
- func ValidMissionS256(s string) bool
- func ValidateServerIdentifier(s string) error
- func VerifyMissionS256(blob []byte, s256 string) error
- func VerifyRequest(req *http.Request, pub crypto.PublicKey) error
- func VerifyRequestWithOptions(req *http.Request, pub crypto.PublicKey, opts RequestVerifyOptions) error
- func VerifyResourceToken(ctx context.Context, token string, opts ResourceTokenVerifyOptions) (*ResourceClaims, PresentedToken, error)
- func WriteApprovalPending(w http.ResponseWriter, pendingURL string, retryAfter int)
- func WriteClarification(w http.ResponseWriter, pendingURL, question string, timeout int, ...)
- func WriteMissionTerminated(w http.ResponseWriter, reason string)
- func WriteProblem(w http.ResponseWriter, status int, code, detail string)
- func WriteSignatureError(w http.ResponseWriter, e SignatureError, detail string)
- func WriteSignatureFailure(w http.ResponseWriter, err error)
- func WriteTokenError(w http.ResponseWriter, err error)
- type AccessServerMetadata
- type Agent
- func (a *Agent) Alg() (string, error)
- func (a *Agent) IssueSubAgentToken(discriminator string, pub crypto.PublicKey) (string, error)
- func (a *Agent) JWK() JWK
- func (a *Agent) JWKS() JWKS
- func (a *Agent) MintToken() (string, error)
- func (a *Agent) NewSubAgent(discriminator string, opts ...AgentOption) (*Agent, error)
- func (a *Agent) Thumbprint() string
- type AgentClaims
- func VerifyAgentToken(ctx context.Context, token string, opts VerifyAgentTokenOptions) (*AgentClaims, error)
- func VerifyAndExtractAgent(ctx context.Context, req *http.Request, opts VerifyAgentTokenOptions) (*AgentClaims, error)
- func VerifySubagentToken(ctx context.Context, token string, signer *AgentClaims, ...) (*AgentClaims, error)
- type AgentIdentifier
- type AgentOption
- type AgentProviderMetadata
- type ApprovedMission
- type AuditRequest
- type AuthClaims
- func IssueAuthToken(p AuthTokenParams, key crypto.Signer, kid string) (string, *AuthClaims, error)
- func VerifyAndExtractAuth(ctx context.Context, req *http.Request, resourceURL string, ...) (*AuthClaims, error)
- func VerifyAuthToken(ctx context.Context, token, resourceURL string, opts AuthTokenVerifyOptions) (*AuthClaims, error)
- func VerifyAuthTokenResponse(ctx context.Context, token string, opts AuthResponseVerifyOptions) (*AuthClaims, error)
- type AuthResponseVerifyOptions
- type AuthTokenParams
- type AuthTokenRequest
- type AuthTokenResponse
- type AuthTokenVerifyOptions
- type ChainRouter
- type Clarification
- type ClarificationPost
- type ClarificationReply
- type Cnf
- type DeferredOptions
- type InteractionRequest
- type InteractionResponse
- type JWK
- type JWKS
- type JWKSCache
- type JWKSResolver
- type KeyRefresher
- type KeyResolver
- type Limiter
- type LimiterFunc
- type MissingComponentsError
- type Mission
- type MissionAction
- type MissionApproval
- type MissionProposal
- type MissionStatusError
- type MissionTool
- type MissionUpdateResponse
- type PSClient
- func (c *PSClient) Audit(ctx context.Context, a AuditRequest) error
- func (c *PSClient) CompleteMission(ctx context.Context, missionS256, summary string) error
- func (c *PSClient) Discover(ctx context.Context) (*PersonServerMetadata, error)
- func (c *PSClient) ForgetPersonTokens(resource string)
- func (c *PSClient) Issuer() string
- func (c *PSClient) Metadata() *PersonServerMetadata
- func (c *PSClient) PersonToken(ctx context.Context, req PersonTokenRequest) (string, error)
- func (c *PSClient) ProposeMission(ctx context.Context, p MissionProposal) (*ApprovedMission, error)
- func (c *PSClient) RelayInteraction(ctx context.Context, r Requirement, description, missionS256 string) (*InteractionResponse, error)
- func (c *PSClient) RequestAuthToken(ctx context.Context, treq AuthTokenRequest) (*AuthTokenResponse, error)
- func (c *PSClient) RequestInteraction(ctx context.Context, ir InteractionRequest) (*InteractionResponse, error)
- func (c *PSClient) RequestPermission(ctx context.Context, p PermissionRequest) (*PermissionResponse, error)
- func (c *PSClient) RequestPersonToken(ctx context.Context, req PersonTokenRequest) (*PersonTokenResponse, error)
- func (c *PSClient) UpdateMission(ctx context.Context, missionS256, description string) (*MissionUpdateResponse, error)
- type PendingStatus
- type PermissionRequest
- type PermissionResponse
- type PersonClaims
- func IssuePersonToken(p PersonTokenParams, key crypto.Signer, kid string) (string, *PersonClaims, error)
- func VerifyAndExtractPerson(ctx context.Context, req *http.Request, resourceURL string, ...) (*PersonClaims, error)
- func VerifyPersonToken(ctx context.Context, token, resourceURL string, opts TokenVerifyOptions) (*PersonClaims, error)
- type PersonServerMetadata
- type PersonTokenParams
- type PersonTokenRequest
- type PersonTokenResponse
- type PresentedToken
- type ProblemError
- type RequestVerifyOptions
- type Requirement
- type ResourceChallengeOptions
- type ResourceClaims
- type ResourceInteraction
- type ResourceMetadata
- type ResourceTokenParams
- type ResourceTokenVerifyOptions
- type RevocationClient
- type RevocationOutcome
- type RevocationRequest
- type RevocationResponse
- type SelfSignedResolver
- type ServerCaller
- type ServerMetadata
- type ServerSigner
- type SignatureError
- type SignatureKey
- type StaticResolver
- type TokenError
- type TokenRequestHints
- type TokenVerifyOptions
- type Transport
- type UnsupportedSchemeError
- type UpstreamVerifyOptions
- type VerifyAgentTokenOptions
- type VerifyServerOptions
Examples ¶
Constants ¶
const ( // ProtocolDraft is the AAuth protocol draft implemented. ProtocolDraft = "draft-hardt-oauth-aauth-protocol-11" // SignatureKeyDraft is the HTTP Signature Keys draft implemented: the // Signature-Key header, its schemes, and the Signature-Error codes. SignatureKeyDraft = "draft-hardt-httpbis-signature-key-09" // BootstrapDraft is the AAuth bootstrap draft implemented: self-hosted // agents, agent provider enrollment, and refresh patterns. BootstrapDraft = "draft-hardt-aauth-bootstrap-02" )
The Internet-Drafts this package implements. Each draft revision may change the wire format; these name the revisions the code and its golden vectors track.
const ( TypAgent = "aa-agent+jwt" TypPerson = "aa-person+jwt" TypResource = "aa-resource+jwt" TypAuth = "aa-auth+jwt" )
JWT typ header values (draft -11 §5.3.1, §6.7.1, §7.1.2, §9.4.1). A recipient MUST check typ before acting on any AAuth JWT (§11.5.1).
const ( WellKnownAgent = "aauth-agent.json" WellKnownPerson = "aauth-person.json" WellKnownResource = "aauth-resource.json" WellKnownAccess = "aauth-access.json" )
Well-known metadata document names (draft -11 §11.2; used as the dwk claim and as /.well-known/{name} paths).
const ( HeaderSignatureKey = "Signature-Key" HeaderSignatureInput = "Signature-Input" HeaderSignature = "Signature" HeaderSignatureError = "Signature-Error" HeaderPrefer = "Prefer" HeaderRetryAfter = "Retry-After" HeaderLocation = "Location" // HeaderAAuthAccess carries a session token from a resource to an // agent (draft -11 §6.3); the agent returns it as // "Authorization: AAuth <session token>", covered by its signature. HeaderAAuthAccess = "AAuth-Access" )
HTTP header names.
const ( // AlgEd25519 is EdDSA over Ed25519 (RFC 9864). Every party MUST // support it. AlgEd25519 = "Ed25519" // AlgES256 is ECDSA on P-256 with SHA-256 (RFC 7518). Every party // SHOULD support it; it suits hardware-backed keys. AlgES256 = "ES256" )
Fully-specified JOSE signature algorithm identifiers (draft -11 §11.3.1; RFC 9864). AAuth names an algorithm only by a fully-specified identifier, carried in the alg member of every JWK and in every JWS header.
const ( // MaxAgentTokenLifetime is the lifetime an agent token SHOULD NOT // exceed (§5.3.1). MaxAgentTokenLifetime = 24 * time.Hour // MaxPersonTokenLifetime is the lifetime a person token MUST NOT // exceed (§7.1.2). MaxPersonTokenLifetime = time.Hour // MaxAuthTokenLifetime is the lifetime an auth token MUST NOT exceed // (§9.4.1). MaxAuthTokenLifetime = time.Hour // MaxResourceTokenLifetime is the lifetime a resource token SHOULD NOT // exceed (§6.7.1). MaxResourceTokenLifetime = 5 * time.Minute )
Token lifetime limits (draft -11 §5.3.1, §6.7.1, §7.1.2, §9.4.1).
const ( ActionClarificationResponse = "clarification_response" ActionUpdatedRequest = "updated_request" )
Clarification response actions (draft -11 §7.5.2).
const ( // SchemeJWT carries a JWT whose cnf.jwk is the signing key. Agents MUST // use it on AAuth resource, PS, and AS requests. SchemeJWT = "jwt" // SchemeJWKSURI identifies a server signing in its own right, whose key // is discovered from {id}/.well-known/{dwk} → jwks_uri → kid. SchemeJWKSURI = "jwks_uri" )
Signature-Key schemes used by AAuth (draft -11 §11.3.2; signature-key §3).
const ( InteractionTypeInteraction = "interaction" // relay an interaction URL and code to the user InteractionTypePayment = "payment" // relay a payment approval InteractionTypeQuestion = "question" // ask the user a question )
Interaction request types (draft -11 §7.6.1). Proposing mission completion is not among them; it belongs at the mission endpoint (§8.5).
const ( // DefaultJWKSMinRefresh is the floor between fetches of one issuer's // key set: implementations MUST NOT fetch more often than once a minute. DefaultJWKSMinRefresh = time.Minute // DefaultJWKSMaxAge is the age after which a cached key set is // discarded regardless of cache headers (SHOULD: 24 hours). DefaultJWKSMaxAge = 24 * time.Hour // DefaultJWKSTTL is the freshness lifetime used when a JWKS response // carries no cache headers. DefaultJWKSTTL = time.Hour // DefaultJWKSMaxEntries bounds the number of cached issuers; entries // are created by unauthenticated callers, so the cache MUST be bounded // (signature-key §7.2). DefaultJWKSMaxEntries = 1024 )
Defaults for JWKSCache (draft -11 §11.4).
const ( // AccessModeAgentToken: the agent signs with its agent token; the // resource authorizes on the agent's identity alone. The default. AccessModeAgentToken = "agent-token" // AccessModePersonToken: the agent signs with a person token; the // resource authorizes on the person's identity alone (§4.2.3). AccessModePersonToken = "person-token" // AccessModeSessionToken: the agent completes the resource's own // interaction flow and receives a session token via AAuth-Access // (§6.2, §6.3). Earlier drafts called this mode aauth-access-token. AccessModeSessionToken = "session-token" // AccessModeAuthToken: the agent obtains an auth token from its PS // using a resource token; the initial call MUST present a person // token (§4.2.4, §4.2.5). AccessModeAuthToken = "auth-token" )
Access mode values for ResourceMetadata.AccessMode (draft -11 §11.2.4; AAuth Access Mode Value registry, §15.11): the credential flow a resource expects, so an agent can plan its first call. The declaration is advisory: a resource MAY return any AAuth-Requirement at runtime and MAY apply different modes to different endpoints.
const ( MissionStatusActive = "active" MissionStatusTerminated = "terminated" TerminationCompleted = "completed" // the person accepted the agent's completion proposal (§8.5) TerminationRevoked = "revoked" // the person, the owning agent, or an administrator withdrew it TerminationExpired = "expired" // the mission reached its expires_at (§8.2) TerminationSuperseded = "superseded" // replaced by another approved mission TerminationAdministrative = "administrative" // an administrator ended it under local policy )
Mission states and termination reasons (draft -11 §8.6). A mission is active or terminated; a terminated mission never returns to active. The PS records why it terminated alongside the mission; the reason set is open, and a recipient that does not recognize a reason keeps the terminated state and treats the reason as an opaque audit value.
const ( MissionActionUpdate = "update" // record a change in the work (§8.4) MissionActionCompletion = "completion" // propose that the mission is finished (§8.5) )
Mission actions at {mission_endpoint}/{mission_s256} (draft -11 §8).
const ( PermissionGranted = "granted" PermissionDenied = "denied" )
Permission values in a PermissionResponse.
const ( ErrCodeInvalidRequest = "invalid_request" // 400: malformed request or parameters ErrCodeServerError = "server_error" // 500: internal error ErrCodeMissionNotFound = "mission_not_found" // 404: no such mission, or not this agent's (§8.7) ErrCodeUnsupportedIss = "unsupported_iss" // 403: revocations from this caller not accepted (§11.12.3) ErrCodeRateLimited = "rate_limited" // 429: too many revocations; Retry-After REQUIRED (§11.12.3) // Polling error codes (§11.9.4): terminal outcomes of a deferred // request, answered at the pending URL. PollErrDenied = "denied" // 403: the user or approver denied the request PollErrAbandoned = "abandoned" // 403: the interaction code was used but not completed PollErrExpired = "expired" // 408: timed out PollErrRevoked = "revoked" // 403: a token the request depends on was revoked PollErrInvalidCode = "invalid_code" // 410: interaction code not recognized or consumed PollErrSlowDown = "slow_down" // 429: polling too frequently )
Error codes of AAuth endpoints other than the token endpoints (draft -11 §6.6.3, §7.6.3, §8.7, §11.9.4, §11.12.3), carried in the error member of an RFC 9457 problem body (§11.9.2). Token endpoint codes are the TokenErr* constants.
const ( RequirementAgentToken = "agent-token" // 401: the agent's identity (§6.1) RequirementPersonToken = "person-token" // 401: the person's identity (§6.4) RequirementAuthToken = "auth-token" // 401 or 202: consent or policy for a scope (§6.5) RequirementInteraction = "interaction" // 202: user action at an interaction URL (§11.6.3) RequirementApproval = "approval" // 202: approval pending from another party (§11.6.4) RequirementClarification = "clarification" // 202: a question for the recipient (§7.5.1) RequirementClaims = "claims" // 202: identity claims required, AS only (§9.2) )
Requirement values used in the AAuth-Requirement header (draft -11 §11.6.2; AAuth Requirement Value registry, §15.8).
const ( // RevocationUnsupported: the downstream party publishes no // revocation_endpoint or answered unsupported_iss; it honors the tokens // until their exp. RevocationUnsupported = "revocation_unsupported" // out, or returned a 5xx or malformed response; the caller MAY revoke // again later. RevocationUnavailable = "revocation_unavailable" )
Downstream revocation outcomes reported in a RevocationResponse (§11.12.3).
const ( SigErrUnsupportedAlgorithm = "unsupported_algorithm" // §5.4.1: alg absent, polymorphic, or not implemented SigErrUnsupportedScheme = "unsupported_scheme" // §5.4.2: Signature-Key scheme not implemented SigErrCacheMiss = "cache_miss" // §5.4.3: cached-scheme identifier not resolved SigErrInvalidSignature = "invalid_signature" // §5.4.4: missing/malformed/stale/failed signature SigErrInvalidInput = "invalid_input" // §5.4.5: required components not covered SigErrInvalidRequest = "invalid_request" // §5.4.6: malformed request, unrelated to the signature SigErrInvalidKey = "invalid_key" // §5.4.7: key unparsable, or kty/crv disagree with alg SigErrUnknownKey = "unknown_key" // §5.4.8: no key with the kid at the jwks_uri SigErrIssuerMissing = "issuer_missing" // §5.4.9: discovered metadata lacks issuer SigErrIssuerMismatch = "issuer_mismatch" // §5.4.10: metadata issuer differs from the identity SigErrInvalidJWT = "invalid_jwt" // §5.4.11: JWT malformed or its signature failed SigErrExpiredJWT = "expired_jwt" // §5.4.12: JWT exp in the past SigErrRevokedJWT = "revoked_jwt" // §5.4.13: JWT withdrawn by its issuer SigErrClockSkew = "clock_skew" // §5.4.14: created or iat ahead of the verifier's clock )
Signature-Error codes (signature-key §5.4; Signature Error Code registry, §9.4.1).
const ( HeaderAcceptSignatureScheme = "Accept-Signature-Scheme" HeaderAcceptSignatureAlg = "Accept-Signature-Alg" )
Response header names (signature-key §4).
const ( TokenErrInvalidRequest = "invalid_request" // malformed JSON, missing required fields TokenErrInvalidResourceToken = "invalid_resource_token" // resource token malformed, failed, or mismatched TokenErrExpiredResourceToken = "expired_resource_token" // resource token expired TokenErrRevokedResourceToken = "revoked_resource_token" // resource token withdrawn by the resource TokenErrInvalidPresentedToken = "invalid_presented_token" // presented token malformed, failed, or wrong typ TokenErrExpiredPresentedToken = "expired_presented_token" // presented token expired TokenErrRevokedPresentedToken = "revoked_presented_token" // presented token revoked by its issuer TokenErrInvalidUpstreamToken = "invalid_upstream_token" // upstream token malformed, failed, or not for the intermediary TokenErrExpiredUpstreamToken = "expired_upstream_token" // upstream token expired TokenErrRevokedUpstreamToken = "revoked_upstream_token" // upstream token, or the calling agent, revoked TokenErrInvalidSubagentToken = "invalid_subagent_token" // sub-agent token malformed, failed, or not the signer's TokenErrExpiredSubagentToken = "expired_subagent_token" // sub-agent token expired TokenErrRevokedSubagentToken = "revoked_subagent_token" // sub-agent token revoked by its agent provider TokenErrClockSkew = "clock_skew" // a parameter token's iat is ahead of the verifier's clock TokenErrUserUnreachable = "user_unreachable" // no channel to the user and no interaction capability TokenErrASUnreachable = "as_unreachable" // the PS could not obtain an auth token from the AS TokenErrServerError = "server_error" // internal error )
Token endpoint error codes (draft -11 §11.9.3).
const ( ParamResourceToken = "resource_token" ParamPresentedToken = "presented_token" ParamUpstreamToken = "upstream_token" ParamSubagentToken = "subagent_token" )
Token request parameters whose verification failures have their own error codes (draft -11 §7.2.1, §11.9.3).
const ContentDigestAlg = "sha-256"
ContentDigestAlg is the digest algorithm for the Content-Digest header.
const DefaultRefreshMargin = 5 * time.Minute
DefaultRefreshMargin is the refresh margin of draft -11 §7.9.1: an agent SHOULD refresh an agent, person, or auth token when fewer than five minutes remain before its exp, and SHOULD NOT present one inside that margin. Five minutes equals the recommended maximum lifetime of a resource token, so a token presented with five minutes left is still valid whenever a resource token issued against it is redeemed.
const DefaultSignatureLabel = "sig"
DefaultSignatureLabel is the signature label used by this implementation. Draft examples use "sig"; RFC 9421 labels are correlated by equality across Signature-Input, Signature, and Signature-Key (signature-key §3).
const DefaultSignatureWindow = 60 * time.Second
DefaultSignatureWindow is the default signature validity window for the created parameter (draft -11 §11.3.4 step 3; resources may advertise another value as signature_window).
const HeaderRequirement = "AAuth-Requirement"
HeaderRequirement is the AAuth-Requirement header name.
const LinkRelResource = "aauth-resource"
LinkRelResource is the aauth-resource link relation (draft -11 §11.2.5): a Link header field (or an HTML link element) naming the resource metadata document that governs what a response describes, for an agent that has reached a developer portal or API host without the resource identifier.
const MaxAgentLocalPartLength = 255
MaxAgentLocalPartLength is the longest permitted agent identifier local part (draft -11 §5.2).
const (
MaxAgentTokenTTLSeconds = 24 * 60 * 60
)
Recommended lifetimes (draft -11 §5.3.1: agent tokens SHOULD NOT exceed 24h).
const MaxSignedStreamBytes = 4 << 20
MaxSignedStreamBytes bounds how much of a request body of unknown length SignRequest buffers to compute its digest (4 MiB, as for Transport.MaxBodyBytes).
const MissionErrTerminated = "mission_terminated"
MissionErrTerminated is the error code of a mission status error (§8.8) and of the mission endpoint (§8.7).
Variables ¶
var ( // ErrInvalidToken means a JWT was malformed or failed verification. ErrInvalidToken = errors.New("aauth: invalid token") // ErrWrongTokenType means the JWT typ header was not the expected value. ErrWrongTokenType = errors.New("aauth: wrong token type") // ErrSignatureInvalid means an HTTP message signature failed to verify. ErrSignatureInvalid = errors.New("aauth: signature invalid") // ErrExpired means a token's exp claim is in the past. ErrExpired = errors.New("aauth: token expired") // ErrClockSkew means a signature's created (or a token's iat) is further // ahead of the verifier's clock than its validity window — the sender's // clock disagrees with the verifier's (signature-key §5.4.14). ErrClockSkew = errors.New("aauth: clock skew") // ErrSignatureInput means Signature-Input does not cover the components // the verifier requires (signature-key §5.4.5); see // [MissingComponentsError]. ErrSignatureInput = errors.New("aauth: signature does not cover required components") // ErrUnsupportedScheme means the Signature-Key member for the label // names a scheme the verifier does not implement (signature-key §5.4.2). ErrUnsupportedScheme = errors.New("aauth: unsupported Signature-Key scheme") // ErrUnknownKey means no key with the requested kid was found at the // issuer's jwks_uri (signature-key §5.4.8). ErrUnknownKey = errors.New("aauth: unknown key") // ErrIssuerMissing means a discovered metadata document has no issuer // member (signature-key §5.4.9; draft -11 §11.2). ErrIssuerMissing = errors.New("aauth: metadata issuer missing") // ErrIssuerMismatch means a discovered metadata document's issuer // differs from the identity it was fetched under (signature-key // §5.4.10; draft -11 §11.2). ErrIssuerMismatch = errors.New("aauth: metadata issuer mismatch") // ErrRevoked means a token verified and is unexpired but its issuer has // withdrawn it (signature-key §5.4.13). This package does not track // revocations; a deployment's revocation check returns (or wraps) it so // that [SignatureErrorFor] answers revoked_jwt. ErrRevoked = errors.New("aauth: token revoked") // ErrMissingSigKey means the request had no Signature-Key header. ErrMissingSigKey = errors.New("aauth: missing Signature-Key header") // ErrBadSigKey means the Signature-Key header was malformed. ErrBadSigKey = errors.New("aauth: malformed Signature-Key header") // ErrMissingClaim means a required JWT claim was absent. ErrMissingClaim = errors.New("aauth: required claim missing") // ErrSubAgentDirect means a sub-agent tried to request authorization // itself; its parent must request on its behalf (draft -11 §10.2.3). ErrSubAgentDirect = errors.New("aauth: sub-agent must not request authorization directly") // ErrUnknownAction means a clarification POST had a missing or // unrecognized action member (draft -11 §7.5.2). ErrUnknownAction = errors.New("aauth: missing or unrecognized action") )
Sentinel errors returned across the package; match with errors.Is.
var ( // ErrUnsupportedAlgorithm means a key's alg is absent, polymorphic // (EdDSA), prohibited (none, symmetric), or not implemented. ErrUnsupportedAlgorithm = errors.New("aauth: unsupported algorithm") // ErrInvalidKey means a key could not be parsed, or its kty/crv // disagree with its alg. ErrInvalidKey = errors.New("aauth: invalid key") )
Errors classifying key and algorithm failures (signature-key §3.3). They map onto the Signature-Error codes unsupported_algorithm and invalid_key.
var DefaultJWKSCache = &JWKSCache{}
DefaultJWKSCache is the cache used by a JWKSResolver whose Cache is nil.
var ErrDisallowedDestination = errors.New("aauth: discovery destination not allowed")
ErrDisallowedDestination means a discovery fetch was refused because its URL was not https or its address was not a public unicast address (see DiscoveryClient).
ErrInteractionUnavailable matches a *ProblemError with code interaction_unavailable (draft -11 §7.6.3): the PS has no channel to relay the interaction, and the agent directs the user to the URL and code itself (§11.6.3.2). It is not terminal.
var ErrInvalidIdentifier = errors.New("aauth: invalid identifier")
ErrInvalidIdentifier means an agent or server identifier does not conform to draft -11 §5.2 or §11.1.1.
var ErrJWKSCacheFull = errors.New("aauth: JWKS cache is full")
ErrJWKSCacheFull means a key set could not be fetched because the cache is at MaxEntries and every entry has a discovery in flight. It is a transient refusal: retrying once a fetch finishes succeeds.
var ErrMetadataIncomplete = errors.New("aauth: metadata document incomplete")
ErrMetadataIncomplete means a metadata document lacks a member its role REQUIRES (draft -11 §11.2).
var ErrMissionDigest = errors.New("aauth: mission blob does not match its s256")
ErrMissionDigest means a mission blob does not hash to the s256 it was returned or named with (draft -11 §8.2.1).
var ErrPresentedTokenMissing = errors.New("aauth: presented_token is required")
ErrPresentedTokenMissing means an auth token request, or an updated_request answering a clarification, had no presented_token (draft -11 §7.2.1, §7.5.2.2): the token the agent presented to the resource that issued the resource token is REQUIRED.
var ErrPresentedTokenRequired = errors.New("aauth: a verified person token or auth token is required")
ErrPresentedTokenRequired means a resource token was requested without a verified person token or auth token from the request (draft -11 §6.7). A resource answers such a request with requirement=person-token (§6.4).
ErrRevocationUnavailable means a revocation reached no terminal outcome: no response, a timeout, a 5xx, or a malformed body. It may be retried.
var ErrRevocationUnsupported = errors.New("aauth: " + RevocationUnsupported)
ErrRevocationUnsupported means the recipient publishes no revocation_endpoint, or does not accept revocations from this caller (unsupported_iss): it honors the token until its exp.
var ErrUnexpectedToken = errors.New("aauth: unexpected token in response")
ErrUnexpectedToken means a token a server returned is not the token the request asked for: the wrong type, audience, issuer, or bound key.
var SigningMethodEd25519 jwt.SigningMethod = signingMethodEd25519{}
SigningMethodEd25519 is the golang-jwt signing method for the fully-specified "Ed25519" JWS algorithm. It is registered with golang-jwt under that name. Signing accepts an ed25519.PrivateKey or any crypto.Signer whose public key is Ed25519; verification takes an ed25519.PublicKey.
Functions ¶
func AcceptSignatureFromResponse ¶
AcceptSignatureFromResponse returns the Accept-Signature-Scheme and Accept-Signature-Alg lists (signature-key §4) from a response; either may be empty. Tokens are returned verbatim (alg case is significant) and members that are not tokens are skipped (clients MUST ignore what they do not recognize, §4.1).
func AcceptedSignatureAlgs ¶
func AcceptedSignatureAlgs() []string
AcceptedSignatureAlgs returns the fully-specified algorithms this implementation verifies, in preference order.
func AcceptedSignatureSchemes ¶
func AcceptedSignatureSchemes() []string
AcceptedSignatureSchemes returns the Signature-Key schemes this implementation verifies on agent-facing AAuth requests: agents MUST use jwt (draft -11 §11.3.2). Endpoints for server-signed requests accept jwks_uri instead (see VerifyServerRequest).
func AlgForPublicKey ¶
AlgForPublicKey returns the fully-specified algorithm a public key is used with: AlgEd25519 for an ed25519.PublicKey, AlgES256 for a P-256 *ecdsa.PublicKey. Other keys yield ErrUnsupportedAlgorithm.
func AttachSignatureKey ¶
AttachSignatureKey sets the Signature-Key header carrying the agent (or person or auth) token via the jwt scheme (signature-key §3.8), serialized as a Structured Field Dictionary member keyed by the signature label:
sig=jwt;jwt="eyJ..."
func AttachSignatureKeyJWKSURI ¶
AttachSignatureKeyJWKSURI sets the Signature-Key header for the jwks_uri scheme under the default label:
sig=jwks_uri;id="https://ps.example";dwk="aauth-person.json";kid="key-1"
func BoundedExpiry ¶
BoundedExpiry computes the exp of a token issued at now (draft -11 §7.1.2, §9.4.1): now+ttl, where a ttl that is zero, negative, or larger than max becomes max, and never later than any non-zero bound. Pass as bounds the exp of the agent token presented when the token was requested, of the presented_token and upstream_token, and a mission's expires_at, as the token type requires. The result is truncated to whole seconds (the JWT NumericDate precision). An error wrapping ErrExpired means a bound is not after now, so no valid token can be issued.
func ChainInteraction ¶
func ChainInteraction(w http.ResponseWriter, pendingURL, interactionURL, code string)
Interaction chaining (draft -11 §10.1.2): when a resource acting as an agent receives a downstream requirement=interaction it cannot satisfy itself, it propagates the interaction to its own caller by returning its own 202 — its own Location (for the caller to poll) and its own interaction code. When the user completes the interaction and the resource obtains the downstream token, it finishes the original request at its pending URL.
ChainInteraction writes that propagating 202. pendingURL and code are the resource's own (not the downstream's); status defaults to "pending".
func ChallengeAgentToken ¶
func ChallengeAgentToken(w http.ResponseWriter)
ChallengeAgentToken answers 401 with requirement=agent-token (§6.1): the resource decides on the agent's identity and the request presented no AAuth agent token.
func ChallengeAuthToken ¶
func ChallengeAuthToken(w http.ResponseWriter, resourceToken string)
ChallengeAuthToken answers 401 with requirement=auth-token and the resource token (§6.5); see IssueResourceToken.
func ChallengePersonToken ¶
func ChallengePersonToken(w http.ResponseWriter)
ChallengePersonToken answers 401 with requirement=person-token (§6.4): the resource needs to know which person the agent acts for and has not verified a person token on the request. A resource MUST answer this way at its authorization endpoint when the request carries no person token, and whenever it would otherwise issue a resource token without having verified a person or auth token (§6.7; see ErrPresentedTokenRequired).
func DiscoveryClient ¶
DiscoveryClient returns the http.Client that key and metadata discovery use when the caller supplies none: JWKSResolver and FetchMetadata with a nil client, and the server roles without an HTTPClient.
Discovery fetches URLs that a request chooses (a token's iss, a signature's id, a metadata document's jwks_uri), so this client only reaches public destinations (signature-key §7.3):
- every request, including each redirect, must be https;
- it connects only to public unicast addresses. Loopback, private, link-local, CGNAT, unspecified, multicast, and other special-purpose addresses are refused at dial time, after name resolution, so a DNS answer cannot redirect it to an internal address;
- it follows at most five redirects, ignores proxy settings from the environment (a proxy would hide the destination), and gives up after 10 seconds.
Refusals wrap ErrDisallowedDestination. To reach other destinations, such as local servers in development, or to route through an egress proxy, supply your own client.
func DoDeferred ¶
func DoDeferred(ctx context.Context, hc *http.Client, req *http.Request, opts DeferredOptions) (*http.Response, error)
DoDeferred executes req and follows the §11.8.4 state machine until a terminal (non-202) response. The caller owns closing the returned body.
func EgressClient ¶
EgressClient returns the http.Client that agent-side requests to a person server use when the caller supplies none (see PSClient). It applies the same destination rules as DiscoveryClient (https only, public unicast addresses only, checked at dial time and on every redirect) but has no overall timeout and a longer response-header timeout, because deferred requests long-poll with Prefer: wait. Callers bound each request with its context.
func FetchMetadata ¶
FetchMetadata GETs {base}/.well-known/{doc} and decodes it into dst, first verifying the document's issuer (draft -11 §11.2; signature-key §3.6): the issuer member MUST be present (ErrIssuerMissing) and MUST equal base by byte equality (ErrIssuerMismatch). This prevents a document hosted at one domain from claiming the issuer of another.
func FollowDeferred ¶
func FollowDeferred(ctx context.Context, hc *http.Client, reqURL *url.URL, res *http.Response, opts DeferredOptions) (*http.Response, error)
FollowDeferred continues the §11.8.4 state machine from an already-received response: if res is not a 202 it is returned unchanged; otherwise the pending URL is polled until a terminal response arrives. reqURL is the URL the original request was sent to (for same-origin Location resolution).
func GenerateKey ¶
GenerateKey creates a fresh in-memory signing key for alg (AlgEd25519 or AlgES256).
func IssueResourceToken ¶
func IssueResourceToken(p ResourceTokenParams, presented PresentedToken, key crypto.Signer, kid string) (string, error)
IssueResourceToken is the resource-side helper for an auth-token challenge (§6.5) or an authorization endpoint response (§6.6.2): it mints an aa-resource+jwt for the agent that presented presented — the person token (or auth token) verified on this request — copying its ps, sub, jti (as presented_jti), mission_s256, and tenant, and binding the agent's key through agent_jkt (the presented token's cnf, which the request signature matched).
A resource MUST verify a person token or an auth token before issuing a resource token (§6.7): a nil or unverified presented is refused with ErrPresentedTokenRequired, and the resource challenges with requirement=person-token instead.
func MintAgentToken ¶
MintAgentToken signs an aa-agent+jwt. The kid header identifies the signing key within the provider's JWKS (draft -11 §11.5.1).
func MintAuthToken ¶
MintAuthToken signs an aa-auth+jwt (Person Server or Access Server side). It is the low-level signer: the claims are taken as given. IssueAuthToken builds a conforming claim set.
func MintPersonToken ¶
MintPersonToken signs an aa-person+jwt (Person Server side). It is the low-level signer: the claims are taken as given. IssuePersonToken builds a conforming claim set.
func MintResourceToken ¶
MintResourceToken signs an aa-resource+jwt (resource side). It is the low-level signer: the claims are taken as given. IssueResourceToken builds a conforming claim set.
func MissionS256 ¶
MissionS256 computes a mission identifier (draft -11 §8.2.1): the unpadded base64url SHA-256 of blob, the exact bytes the PS persists and returns. Never re-serialize a decoded blob to compute it.
func ParseSignatureKey ¶
ParseSignatureKey returns the JWT carried under the jwt scheme for the default label. Any other scheme is ErrUnsupportedScheme (draft -11 §11.3.4 step 4: agents MUST use the jwt scheme).
func ResourceLinks ¶
ResourceLinks returns the resource identifiers named by aauth-resource Link header fields in h (draft -11 §11.2.5), in order and without duplicates. A target that is not a server identifier followed by /.well-known/aauth-resource.json is skipped: an agent MUST NOT fetch a target of any other form. Fetch a returned identifier's document with FetchMetadata and WellKnownResource, which verifies that its issuer equals the identifier.
The relation says nothing about the response that carries it: a 401 still carries its requirement in AAuth-Requirement. Verifiers never use it; keys are discovered from a signer's iss and dwk (§13.6).
func ResourceMetadataLink ¶
ResourceMetadataLink returns a Link header field value naming the metadata document of resource, a resource identifier (§11.2.5):
<https://api.example/.well-known/aauth-resource.json>; rel="aauth-resource"
func RevocationEndpoint ¶
RevocationEndpoint discovers the revocation_endpoint of the server issuer from its metadata document dwk (draft -11 §11.2.2–§11.2.4), verifying the document's issuer. A server that publishes none answers ErrRevocationUnsupported.
func SignRequest ¶
SignRequest signs req per the AAuth profile: sets Content-Digest when a body is present, then Signature-Input and Signature under the default label, with created set to the current time. The Signature-Key header MUST already be attached (it is a covered component). key may be any supported crypto.Signer (see GenerateKey); the algorithm is determined by the key and never sent on the wire (draft -11 §11.3.3.2).
keyid SHOULD be empty: draft -11 §11.3.3.2 says agents SHOULD NOT send the keyid parameter, since Signature-Key identifies the key. A non-empty value is emitted as given (it MUST then identify the same key).
func TokenType ¶
TokenType returns the typ header of a compact JWS without verifying it, so a recipient can dispatch a Signature-Key token to the matching verifier (agent, person, or auth token). The result is only a routing hint: each verifier checks typ again.
func ValidMissionS256 ¶
ValidMissionS256 reports whether s has the form of a mission identifier: 43 base64url characters, the unpadded encoding of a SHA-256 digest. A PS answers a malformed {mission_s256} path segment with invalid_request (§8.7).
func ValidateServerIdentifier ¶
ValidateServerIdentifier checks an issuer value against draft -11 §11.1.1: the https scheme and a host only — no port, path, query, fragment, or trailing slash — lowercase, with internationalized names in A-label form. Failures wrap ErrInvalidIdentifier. Server identifiers are compared by exact string equality.
func VerifyMissionS256 ¶
VerifyMissionS256 checks that blob hashes to s256 (ErrMissionDigest otherwise).
func VerifyRequest ¶
VerifyRequest verifies the HTTP message signature against pub (an ed25519.PublicKey or P-256 *ecdsa.PublicKey) with default options; see VerifyRequestWithOptions.
func VerifyRequestWithOptions ¶
func VerifyRequestWithOptions(req *http.Request, pub crypto.PublicKey, opts RequestVerifyOptions) error
VerifyRequestWithOptions verifies the HTTP message signature on req against pub per draft -11 §11.3.4:
- Signature, Signature-Input, and Signature-Key must be present (ErrSignatureInvalid).
- The covered components must include the base set, authorization when an AAuth credential is sent, and any the options require (*MissingComponentsError, matching ErrSignatureInput).
- created must be present and within the validity window: older is ErrSignatureInvalid, further ahead than the window is ErrClockSkew. An expires in the past is ErrSignatureInvalid.
- The signature must verify under pub, whose type fixes the algorithm (no alg parameter is consulted), and a Content-Digest must match the body (ErrSignatureInvalid).
func VerifyResourceToken ¶
func VerifyResourceToken(ctx context.Context, token string, opts ResourceTokenVerifyOptions) (*ResourceClaims, PresentedToken, error)
VerifyResourceToken verifies an aa-resource+jwt at its recipient — the PS or AS token endpoint — per draft -11 §6.7.2, and returns its claims and the verified presented token:
- the resource token per §11.5.2, with aud equal to opts.Audience;
- agent_jkt equal to opts.AgentJKT;
- the presented token by its typ — a person token per §7.1.4 or an auth token per §9.4.3 (without the resource's record check) — with aud equal to the resource token's iss and cnf.jwk matching agent_jkt; then its jti equals presented_jti, its PS equals ps, and its sub, mission_s256, and tenant equal the resource token's; and ps names opts.PS;
- when mission_s256 is present, opts.CheckMission.
Failures are [*TokenError]s: invalid_resource_token or expired_resource_token for the resource token and for any mismatch between the two tokens (evidence of tampering that SHOULD be surfaced to operators), invalid_presented_token or expired_presented_token for the presented token, and clock_skew.
func WriteApprovalPending ¶
func WriteApprovalPending(w http.ResponseWriter, pendingURL string, retryAfter int)
WriteApprovalPending writes a requirement=approval deferred response (§11.6.4): the server is obtaining approval from another party — by push notification, email, or administrator review — and the agent polls pendingURL until a terminal response, with no user action on its side. The response carries Location and Retry-After (retryAfter seconds), as it MUST.
func WriteClarification ¶
func WriteClarification(w http.ResponseWriter, pendingURL, question string, timeout int, options []string)
WriteClarification writes a requirement=clarification 202 (§7.3.1) asking the recipient a question. options may be nil; timeout 0 omits the field.
func WriteMissionTerminated ¶
func WriteMissionTerminated(w http.ResponseWriter, reason string)
WriteMissionTerminated writes the §8.8 mission status error (server side): 403, application/problem+json, error mission_terminated, mission_status terminated, and reason (one of the Termination* values, or empty to omit it).
func WriteProblem ¶
func WriteProblem(w http.ResponseWriter, status int, code, detail string)
WriteProblem writes an RFC 9457 error response (draft -11 §11.9.2): status, application/problem+json, and a body with error and detail.
func WriteSignatureError ¶
func WriteSignatureError(w http.ResponseWriter, e SignatureError, detail string)
WriteSignatureError writes a 401 response (draft -11 §11.3.4: every signature failure is 401) carrying the Signature-Error header and an RFC 9457 problem+json body whose type is urn:ietf:params:sig-error:<code>. For unsupported_scheme and unsupported_algorithm it also names what would succeed, in Accept-Signature-Scheme or Accept-Signature-Alg (signature-key §4.4).
Policy denials after successful verification are NOT signature errors — return a plain 403 without this header (signature-key §5.3).
func WriteSignatureFailure ¶
func WriteSignatureFailure(w http.ResponseWriter, err error)
WriteSignatureFailure classifies err with SignatureErrorFor and writes the 401 Signature-Error response. An error that is not classified (such as a failure to reach an issuer's metadata) is reported as invalid_signature, since the request could not be authenticated.
func WriteTokenError ¶
func WriteTokenError(w http.ResponseWriter, err error)
WriteTokenError writes a token endpoint error response (draft -11 §11.9.2): the status for the code and an application/problem+json body carrying error and detail. An err that is not a *TokenError is answered as server_error.
Types ¶
type AccessServerMetadata ¶
type AccessServerMetadata struct {
ServerMetadata
// AuthTokenEndpoint is where PSes send token requests (§9.1).
// REQUIRED.
AuthTokenEndpoint string `json:"auth_token_endpoint"`
// RevocationEndpoint is where a PS revokes a person token it presented
// to this AS, and a resource a resource token whose aud is this AS
// (§11.12). RECOMMENDED.
RevocationEndpoint string `json:"revocation_endpoint,omitempty"`
}
AccessServerMetadata is /.well-known/aauth-access.json (draft -11 §11.2.3), published by a resource's Access Server.
func (AccessServerMetadata) Validate ¶
func (m AccessServerMetadata) Validate() error
Validate checks the members an AS MUST publish: issuer, jwks_uri, and auth_token_endpoint.
type Agent ¶
type Agent struct {
// ID is the agent identifier (the token's sub), stable across rotations.
ID AgentIdentifier
// Issuer is the agent provider URL. For a self-hosted agent this is a
// domain the operator controls, publishing /.well-known/aauth-agent.json.
// Empty for purely local deployments verified via SelfSignedResolver.
Issuer string
// PS is the agent's Person Server URL (optional ps claim).
PS string
// TokenTTL bounds minted tokens; capped at 24h per draft -11 §5.3.1.
TokenTTL time.Duration
// Key is the agent's signing key. It signs both self-issued agent
// tokens and HTTP messages; its public half appears in cnf.jwk. Any
// supported crypto.Signer works (Ed25519 or P-256; see [GenerateKey]),
// including keys held in a platform keystore.
Key crypto.Signer
// contains filtered or unexported fields
}
Agent is a self-hosted agent: it acts as its own agent provider and self-issues agent tokens signed by its published key (draft-hardt-aauth-bootstrap-02 §4.3). One key serves both as the AP signing key and as the cnf.jwk HTTP-signing key.
func NewAgent ¶
func NewAgent(id AgentIdentifier, opts ...AgentOption) (*Agent, error)
NewAgent creates an agent for the given identifier. Unless WithKey supplies a key, it generates a fresh one — Ed25519 by default, or the algorithm chosen with WithKeyAlgorithm.
Example (Es256) ¶
ExampleNewAgent_es256 creates an agent with a P-256 key (ES256), the usual choice where keys live in a secure enclave. Any crypto.Signer with a P-256 or Ed25519 public key can be supplied with WithKey instead.
package main
import (
"fmt"
"log"
aauth "github.com/aauth-dev/aauth-go"
)
func main() {
id, _ := aauth.ParseAgentIdentifier("aauth:assistant@agent.example")
agent, err := aauth.NewAgent(id, aauth.WithKeyAlgorithm(aauth.AlgES256))
if err != nil {
log.Fatal(err)
}
fmt.Println(agent.JWK().Alg)
}
Output: ES256
func (*Agent) IssueSubAgentToken ¶
IssueSubAgentToken issues, as this agent's own (self-hosted) agent provider, an agent token for a sub-agent that holds its own key pub (draft -11 §10.2.1; bootstrap §9.1): sub is this agent's identifier with "+discriminator", parent_agent names this agent, iss and ps are this agent's, cnf.jwk is pub, and exp does not exceed the agent's own token lifetime. The sub-agent signs its requests with its own key; a sub-agent never shares its parent's. A sub-agent cannot have sub-agents (§10.2.2), and an agent whose tokens come from elsewhere cannot issue them — its provider does.
func (*Agent) JWK ¶
JWK returns the agent's public key with Kid = RFC 7638 thumbprint.
It panics if Key is nil or of an unsupported type — a programming error that NewAgent rules out.
func (*Agent) MintToken ¶
MintToken returns a fresh agent token for the agent: from its token source (WithTokenSource, Agent.NewSubAgent) when it has one, else self-issued — an aa-agent+jwt with iss, dwk, sub, jti, cnf.jwk, iat, exp (+ ps when configured), signed by Key.
func (*Agent) NewSubAgent ¶
func (a *Agent) NewSubAgent(discriminator string, opts ...AgentOption) (*Agent, error)
NewSubAgent creates a sub-agent of this self-hosted agent (draft -11 §10.2): its identifier is this one's with "+discriminator", it holds its own key (generated, or supplied with WithKey / WithKeyAlgorithm), and its tokens are issued on demand by this agent with Agent.IssueSubAgentToken. Only the issuing process needs this agent's key; a sub-agent in another process receives its token out of band and uses WithTokenSource.
A sub-agent MUST NOT call the PS itself. Its parent obtains its person and auth tokens (§10.2.3): give the sub-agent a Transport whose PS is the parent's PSClient — NewTransport(sub, parentPS) — and the transport sends the sub-agent's token as subagent_token in requests the parent signs.
func (*Agent) Thumbprint ¶
Thumbprint is the agent key's RFC 7638 thumbprint.
type AgentClaims ¶
type AgentClaims struct {
DWK string `json:"dwk"` // well-known doc name for key discovery
PS string `json:"ps,omitempty"` // the agent's Person Server URL (optional)
ParentAgent string `json:"parent_agent,omitempty"` // parent id; set marks a sub-agent
Cnf Cnf `json:"cnf"` // confirmation claim carrying the agent's public key
// RegisteredClaims carries iss, sub, jti, iat, exp.
jwt.RegisteredClaims
}
AgentClaims is the payload of an aa-agent+jwt (draft -11 §5.3.1).
Required: iss (agent provider server identifier), dwk ("aauth-agent.json"), sub (agent identifier), jti, cnf.jwk, iat, exp (SHOULD NOT exceed 24 hours). Optional: ps (Person Server URL; an agent that has a PS MUST carry it, §4.5), parent_agent (sub-agent marker; a sub-agent's iss MUST equal its parent's, §10.2.1).
func VerifyAgentToken ¶
func VerifyAgentToken(ctx context.Context, token string, opts VerifyAgentTokenOptions) (*AgentClaims, error)
VerifyAgentToken verifies an aa-agent+jwt per draft -11 §5.3.3 and the common JWT rules (§11.5.2) and returns its claims: typ, alg, signature, iat REQUIRED (refused as ErrClockSkew when further ahead of the verifier's clock than opts.Signature's window), and exp in the future with no skew tolerance (ErrExpired). With RequireProviderClaims it also requires dwk aauth-agent.json, jti, and iss and ps to be server identifiers. The caller still MUST verify the HTTP message signature against claims.Cnf.JWK — see VerifyAndExtractAgent.
func VerifyAndExtractAgent ¶
func VerifyAndExtractAgent(ctx context.Context, req *http.Request, opts VerifyAgentTokenOptions) (*AgentClaims, error)
VerifyAndExtractAgent is the server-side entry point: parse Signature-Key, verify the agent token (via opts.Resolver), then verify the HTTP message signature against the token's cnf.jwk (draft -11 §5.3.3).
func VerifySubagentToken ¶
func VerifySubagentToken(ctx context.Context, token string, signer *AgentClaims, opts VerifyAgentTokenOptions) (*AgentClaims, error)
VerifySubagentToken verifies a subagent_token request parameter (draft -11 §7.1, §7.2, §10.2) at a PS: an agent token, verified as by VerifyAgentToken, that carries parent_agent naming signer — the verified agent token that signed the request — and whose iss equals signer's iss (§10.2.1). A signer that is itself a sub-agent is ErrSubAgentDirect (§10.2.2). Every other failure is a *TokenError: invalid_subagent_token, expired_subagent_token, or revoked_subagent_token (§11.9.3).
func (*AgentClaims) IsSubAgent ¶
func (c *AgentClaims) IsSubAgent() bool
IsSubAgent reports whether the token marks a sub-agent (draft -11 §10.2). Sub-agents MUST NOT request authorization directly.
type AgentIdentifier ¶
type AgentIdentifier struct {
Name string // the agent name
Discriminator string // sub-agent discriminator; non-empty marks a sub-agent
Domain string // the agent's domain
}
AgentIdentifier is the parsed form of an AAuth agent identifier (draft -11 §5.2):
aauth:<name>@<domain> — a top-level agent aauth:<name>+<discriminator>@<domain> — a sub-agent (§10.2)
The local part (name, plus "+discriminator" for a sub-agent) consists of ASCII letters, digits, "-", "_", "+", and "."; it is 1–255 characters, compared case-sensitively, and never case-folded. The domain is the agent provider's domain: a lowercase DNS name in ASCII (A-label) form, with no scheme, port, or path (§11.1.1).
The identifier is stable across key rotations (it is the token's sub); keys are conveyed separately via cnf.jwk. The "+" split is for readability only — the parent_agent claim, not the local part, is the authoritative sub-agent marker.
func ParseAgentIdentifier ¶
func ParseAgentIdentifier(s string) (AgentIdentifier, error)
ParseAgentIdentifier parses and validates an "aauth:" identifier string per draft -11 §5.2. Failures wrap ErrInvalidIdentifier.
Example ¶
ExampleParseAgentIdentifier parses an agent identifier into its parts.
package main
import (
"fmt"
aauth "github.com/aauth-dev/aauth-go"
)
func main() {
id, _ := aauth.ParseAgentIdentifier("aauth:claude-code@devbox.local")
fmt.Println(id.Name, id.Domain, id.IsSubAgent())
}
Output: claude-code devbox.local false
func (AgentIdentifier) IsSubAgent ¶
func (a AgentIdentifier) IsSubAgent() bool
IsSubAgent reports whether the identifier carries a sub-agent discriminator.
func (AgentIdentifier) Parent ¶
func (a AgentIdentifier) Parent() (AgentIdentifier, error)
Parent returns the parent identifier of a sub-agent.
func (AgentIdentifier) String ¶
func (a AgentIdentifier) String() string
String renders the canonical identifier form.
func (AgentIdentifier) SubAgent ¶
func (a AgentIdentifier) SubAgent(discriminator string) (AgentIdentifier, error)
SubAgent derives a sub-agent identifier under this agent (draft -11 §10.2: single level only — deriving from a sub-agent is an error). The discriminator is non-empty, uses the local-part characters other than "+", and the resulting local part fits in 255 characters.
Example ¶
ExampleAgentIdentifier_SubAgent derives a short-lived worker under an orchestrating agent (draft -11 §10.2).
package main
import (
"fmt"
aauth "github.com/aauth-dev/aauth-go"
)
func main() {
parent, _ := aauth.ParseAgentIdentifier("aauth:orchestrator@example.com")
worker, _ := parent.SubAgent("search-1")
fmt.Println(worker)
fmt.Println(worker.IsSubAgent())
}
Output: aauth:orchestrator+search-1@example.com true
type AgentOption ¶
type AgentOption func(*Agent)
AgentOption configures NewAgent.
func WithIssuer ¶
func WithIssuer(iss string) AgentOption
WithIssuer sets the agent-provider URL (self-hosted domain).
func WithKey ¶
func WithKey(key crypto.Signer) AgentOption
WithKey uses an existing signing key instead of generating one: an ed25519.PrivateKey, a P-256 *ecdsa.PrivateKey, or any crypto.Signer with such a public key (for example a hardware-backed key).
func WithKeyAlgorithm ¶
func WithKeyAlgorithm(alg string) AgentOption
WithKeyAlgorithm selects the algorithm of the key NewAgent generates (AlgEd25519, the default, or AlgES256). It has no effect with WithKey.
func WithPersonServer ¶
func WithPersonServer(ps string) AgentOption
WithPersonServer sets the ps claim.
func WithTokenSource ¶
func WithTokenSource(fn func() (string, error)) AgentOption
WithTokenSource makes the agent present tokens issued elsewhere instead of self-issuing them: fn returns a current agent token whose cnf.jwk is the agent's Key — for example one a hosted agent provider issued, or a sub-agent token handed to a worker process (bootstrap §9). fn is called once per token the agent presents and should cache.
func WithTokenTTL ¶
func WithTokenTTL(d time.Duration) AgentOption
WithTokenTTL overrides the minted-token lifetime (capped at 24h).
type AgentProviderMetadata ¶
type AgentProviderMetadata struct {
ServerMetadata
// CallbackEndpoint is the agent's HTTPS callback endpoint (§7.3).
CallbackEndpoint string `json:"callback_endpoint,omitempty"`
// EventEndpoint is where the AP receives event tokens from resources
// (AAuth Events).
EventEndpoint string `json:"event_endpoint,omitempty"`
// LocalhostCallbackAllowed permits localhost callbacks. Default false.
LocalhostCallbackAllowed bool `json:"localhost_callback_allowed,omitempty"`
}
AgentProviderMetadata is /.well-known/aauth-agent.json (draft -11 §11.2.1) — published by an agent provider, or by a self-hosted agent acting as its own provider, so verifiers can discover the token-signing JWKS.
func (AgentProviderMetadata) Validate ¶
func (m AgentProviderMetadata) Validate() error
Validate checks the members an agent provider MUST publish: issuer and jwks_uri.
type ApprovedMission ¶
type ApprovedMission struct {
// S256 identifies the mission, with the PS, everywhere it appears: the
// mission_s256 of tokens and PS requests.
S256 string
// Blob is the exact mission bytes S256 covers. Keep it: re-encoding
// Mission would not reproduce the identifier.
Blob []byte
// Mission is the parsed blob.
Mission *Mission
// Capabilities the PS can provide on the person's behalf right now.
Capabilities []string
// PersonTokens maps approved resources to person tokens carrying
// mission_s256; they are also cached for [PSClient.PersonToken].
PersonTokens map[string]string
}
ApprovedMission is a mission the PS approved (draft -11 §8.2), verified against its identifier.
type AuditRequest ¶
type AuditRequest struct {
// MissionS256 names the mission whose log the record belongs to
// (§8.2.1). REQUIRED.
MissionS256 string `json:"mission_s256"`
// Action identifies what was performed (REQUIRED).
Action string `json:"action"`
// Description says what was done and the outcome (Markdown, optional).
Description string `json:"description,omitempty"`
// Parameters are the arguments that were used.
Parameters map[string]any `json:"parameters,omitempty"`
// Result carries the outcome of the action.
Result map[string]any `json:"result,omitempty"`
}
AuditRequest is the body of POST {audit_endpoint} (§7.8.1).
type AuthClaims ¶
type AuthClaims struct {
DWK string `json:"dwk"` // "aauth-person.json" (PS) or "aauth-access.json" (AS)
PS string `json:"ps"` // the PS the person is represented by
Scope string `json:"scope,omitempty"` // authorized scopes, space-separated
Account string `json:"account,omitempty"` // the account the authorization is for (§11.11)
MissionS256 string `json:"mission_s256,omitempty"` // the mission it was issued under (§8)
Tenant string `json:"tenant,omitempty"` // the person's organization; (iss, tenant) names it
Cnf Cnf `json:"cnf"` // the agent's key
// RegisteredClaims carries iss, sub, aud, jti, iat, exp.
jwt.RegisteredClaims
// contains filtered or unexported fields
}
AuthClaims is the payload of an aa-auth+jwt (draft -11 §9.4.1).
Required: iss (the PS or AS), dwk ("aauth-person.json" from a PS, "aauth-access.json" from an AS), aud (the resource), ps (the person's PS; equal to iss when a PS issued the token), sub (the person's directed identifier, copied from the resource token), jti, iat, exp, cnf.jwk (the agent's key). Optional: scope, account, mission_s256, tenant.
exp MUST NOT exceed one hour and MUST NOT be later than the agent token used to obtain it, the presented token, the upstream token, or the mission's expires_at (see IssueAuthToken).
func IssueAuthToken ¶
func IssueAuthToken(p AuthTokenParams, key crypto.Signer, kid string) (string, *AuthClaims, error)
IssueAuthToken builds and signs an auth token (draft -11 §9.4.1): iss, dwk, aud = the resource token's iss, ps, sub copied from the resource token (never from an upstream token, §10.1.1.2), the granted scope, account, mission_s256, and tenant, the agent's (or sub-agent's) key — which must be the key the resource token's agent_jkt names — and an exp no later than one hour, the agent token's, the presented token's, the upstream token's, and the mission's expiry.
func VerifyAndExtractAuth ¶
func VerifyAndExtractAuth(ctx context.Context, req *http.Request, resourceURL string, opts AuthTokenVerifyOptions) (*AuthClaims, error)
VerifyAndExtractAuth authenticates a resource request signed with an auth token in Signature-Key (§9.4.2): verify the token (VerifyAuthToken), then the HTTP message signature against its cnf.jwk.
func VerifyAuthToken ¶
func VerifyAuthToken(ctx context.Context, token, resourceURL string, opts AuthTokenVerifyOptions) (*AuthClaims, error)
VerifyAuthToken verifies an aa-auth+jwt per draft -11 §9.4.3 and the common JWT rules (§11.5.2) from the resource's perspective: issuer trust via opts.Resolver, dwk aauth-access.json or aauth-person.json, iss a server identifier, iat REQUIRED, a lifetime of at most one hour, aud = this resource, a structurally complete cnf.jwk, sub and ps present (ps equal to iss for a PS-issued token), exp in the future, and then the (iss, sub) record check. A person token is rejected (wrong typ, §13.11). Binding cnf.jwk to the HTTP signature is completed by VerifyAndExtractAuth.
func VerifyAuthTokenResponse ¶
func VerifyAuthTokenResponse(ctx context.Context, token string, opts AuthResponseVerifyOptions) (*AuthClaims, error)
VerifyAuthTokenResponse is the agent-side check of an auth token it received from its PS (draft -11 §9.4.4): optionally its signature, then iss = the resource token's aud, aud = the resource, cnf.jwk = the agent's key, sub = the sub of the token the agent presented, and exp in the future.
func (*AuthClaims) PersonServer ¶
func (c *AuthClaims) PersonServer() string
PersonServer returns ps, the PS the person is represented by. An intermediary routes downstream token requests there (§10.1.1).
type AuthResponseVerifyOptions ¶
type AuthResponseVerifyOptions struct {
// TokenVerifyOptions verifies the auth token's signature under its
// issuer's key (the PS in three-party, the AS in four-party). A nil
// Resolver skips signature verification: the agent trusts its PS, so
// it is not required, but it is RECOMMENDED to detect errors early.
TokenVerifyOptions
// Resource is the resource token the agent exchanged, as returned by
// [VerifyResourceChallenge]: the auth token's iss must be its aud,
// and the auth token's aud its iss (the resource the agent intends to
// access).
Resource *ResourceClaims
// Agent is the agent; cnf.jwk must be its key.
Agent *Agent
// Presented is the token the agent presented to the resource; the auth
// token's sub must equal its sub.
Presented string
}
AuthResponseVerifyOptions tunes VerifyAuthTokenResponse.
type AuthTokenParams ¶
type AuthTokenParams struct {
// Issuer is the PS's or AS's server identifier (iss).
Issuer string
// DWK is WellKnownPerson for a PS (the default) or WellKnownAccess for
// an AS.
DWK string
// PS is the person's PS (ps). Empty means Issuer, which is correct for
// a PS; an AS sets the PS that sent the token request.
PS string
// Resource is the resource token verified with [VerifyResourceToken]:
// aud becomes its iss, and sub, account, mission_s256, and tenant are
// copied from it.
Resource *ResourceClaims
// Presented is the verified presented_token returned with Resource;
// its exp bounds the auth token's.
Presented PresentedToken
// Agent is the verified agent token that signed the token request (at
// an AS, the agent_token parameter). Its exp bounds the auth token's,
// and its key is bound unless Subagent is set.
Agent *AgentClaims
// Subagent is the verified subagent_token of a parent-mediated request
// (§10.2.3): the auth token binds the sub-agent's key.
Subagent *AgentClaims
// UpstreamExpiresAt is the exp of the verified upstream_token in call
// chaining (§10.1.1). MissionExpiresAt is the mission's expires_at.
// Each, when set, bounds the auth token's exp.
UpstreamExpiresAt time.Time
MissionExpiresAt time.Time
// Scope is the granted scope, space-separated.
Scope string
// TTL is the requested lifetime; zero or more than one hour means one
// hour.
TTL time.Duration
// Now is the issuance time; zero means time.Now.
Now time.Time
}
AuthTokenParams describes an auth token a PS or AS issues from its auth token endpoint (draft -11 §7.2, §9.1, §9.4.1).
type AuthTokenRequest ¶
type AuthTokenRequest struct {
// ResourceToken is the resource token to redeem. REQUIRED.
ResourceToken string `json:"resource_token"`
// PresentedToken is the token the agent presented to the resource that
// issued ResourceToken — the person token on the first challenge of a
// grant, the auth token on a step-up or per-call challenge — whose jti
// the resource token's presented_jti names. REQUIRED. Its exp bounds
// the auth token issued.
PresentedToken string `json:"presented_token"`
// UpstreamToken is the person or auth token the calling agent presented
// to the requester, in call chaining (§10.1.1).
UpstreamToken string `json:"upstream_token,omitempty"`
// SubagentToken is a sub-agent's agent token, when a parent requests
// on its behalf (§10.2.3).
SubagentToken string `json:"subagent_token,omitempty"`
TokenRequestHints
}
AuthTokenRequest is the body of POST {auth_token_endpoint} (draft -11 §7.2.1).
type AuthTokenResponse ¶
type AuthTokenResponse struct {
AuthToken string `json:"auth_token"` // the issued aa-auth+jwt
ExpiresIn int64 `json:"expires_in"` // token lifetime in seconds
}
AuthTokenResponse is the direct-grant 200 body (draft -11 §7.2.2).
type AuthTokenVerifyOptions ¶
type AuthTokenVerifyOptions struct {
TokenVerifyOptions
// CheckSubject is the record check of draft -11 §9.4.3.2 step 4: it
// confirms that (iss, sub) matches, or establishes, the resource's
// record for this person (§13.7). It runs last, on an otherwise valid
// token; its error is returned unchanged. sub is unique within iss
// only: a resource MUST NOT match a sub from one issuer against a
// record established under another.
CheckSubject func(ctx context.Context, iss, sub string) error
}
AuthTokenVerifyOptions tunes resource-side auth token verification.
type ChainRouter ¶
type ChainRouter struct {
// PersonServer is the PS to send the downstream person token and auth
// token requests to: the iss of an upstream person token, the ps of an
// upstream auth token. Its endpoints are discovered from its metadata.
PersonServer string
// UpstreamToken is the raw upstream token to send as upstream_token.
UpstreamToken string
// MissionS256 is the mission the upstream token carries, if any; the PS
// evaluates the downstream request against it. The intermediary does
// not send mission_s256 of its own (§7.1).
MissionS256 string
// UpstreamExpiresAt is the upstream token's exp. Downstream person and
// auth tokens expire no later; after it the intermediary uses a later
// token from the calling agent.
UpstreamExpiresAt time.Time
// contains filtered or unexported fields
}
ChainRouter tells an intermediary where to send downstream token requests and what to carry, derived from the upstream token per §10.1.1.
func RouteDownstream ¶
func RouteDownstream(upstream PresentedToken, rawUpstream string) (ChainRouter, error)
RouteDownstream computes the routing for a downstream call from the verified upstream token — the person token or auth token the calling agent presented on a request the intermediary served — and its raw form (§10.1.1). The ps claim in the intermediary's own agent token is NOT used: it names the intermediary's person server, not the person's.
func (ChainRouter) PSClient ¶
func (r ChainRouter) PSClient(intermediary *Agent) *PSClient
PSClient returns a client for the person server the upstream token names, acting as intermediary — the intermediary's own agent identity, which signs every downstream request (§10.1.1).
func (ChainRouter) Transport ¶
func (r ChainRouter) Transport(intermediary *Agent) (*Transport, error)
Transport returns a Transport for the intermediary's downstream calls (draft -11 §10.1.1): it obtains a person token for each downstream resource with the upstream token as upstream_token, presents it, and redeems the resource token at the same person server with the person token as presented_token and the upstream token as upstream_token, all signed with the intermediary's own key. No downstream token outlives the upstream token.
The intermediary MUST be its own agent provider (§10.1.1.1): its agent token's iss is its resource identifier, the upstream token's aud. A different issuer is refused here, as a PS would refuse it with invalid_upstream_token. Use one Transport per upstream token: its caches hold tokens for that token's person.
type Clarification ¶
type Clarification struct {
Question string // the Markdown question to answer
Timeout int // seconds until the server times out the request (0 if unset)
Options []string // discrete answer choices, when the question has them
}
Clarification is a question posed during a deferred flow (§7.3.1) that the agent must answer before the request can proceed.
type ClarificationPost ¶
type ClarificationPost struct {
Action string `json:"action"` // clarification_response or updated_request
ClarificationResponse string `json:"clarification_response,omitempty"` // the answer text (Markdown, agent-asserted)
ResourceToken string `json:"resource_token,omitempty"` // replacement resource token
PresentedToken string `json:"presented_token,omitempty"` // the token presented to obtain ResourceToken
Justification string `json:"justification,omitempty"` // reason for an updated request
}
ClarificationPost is an agent's response to a clarification (draft -11 §7.5.2), POSTed to the pending URL. Action is ActionClarificationResponse or ActionUpdatedRequest.
func ParseClarificationPost ¶
func ParseClarificationPost(r *http.Request) (*ClarificationPost, error)
ParseClarificationPost decodes a POST body to the pending URL and validates it (draft -11 §7.5.2): a missing or unrecognized action is ErrUnknownAction (answer 400); an updated_request without resource_token or presented_token is a *TokenError invalid_request (wrapping ErrPresentedTokenMissing for the latter). The server then verifies the resource token and presented token as a pair (§6.7.2) before replacing the pending request.
type ClarificationReply ¶
type ClarificationReply struct {
Text string // the answer, for a clarification_response
ResourceToken string // a replacement resource token, for an updated_request
PresentedToken string // the token presented to obtain ResourceToken (REQUIRED with it)
Justification string // optional (RECOMMENDED) reason accompanying an updated_request
Cancel bool // withdraw the request (DELETE the pending URL)
}
ClarificationReply is the agent's answer to a Clarification (draft -11 §7.5.2):
- Text set → clarification_response (answer the question).
- ResourceToken set → updated_request (replace the request). The PresentedToken the agent used at the resource to obtain the new resource token is REQUIRED with it; the PS verifies the pair as for an auth token request. The new resource token MUST share iss, ps, sub, agent_jkt, mission_s256, and tenant with the original, and its presented_jti MUST be PresentedToken's jti (§7.5.2.2).
- Cancel true → DELETE the pending URL, withdrawing the request.
type Cnf ¶
type Cnf struct {
JWK *JWK `json:"jwk,omitempty"` // the confirmation public key
JKT string `json:"jkt,omitempty"` // RFC 7638 thumbprint (alternative to JWK)
}
Cnf is the JWT confirmation claim (RFC 7800). Agent tokens carry the full public key in JWK; other token types may bind by thumbprint via JKT.
type DeferredOptions ¶
type DeferredOptions struct {
// PreferWaitSeconds is sent as `Prefer: wait=N` on polling GETs to
// request long-poll behavior. 0 omits the header.
PreferWaitSeconds int
// DefaultPollInterval applies when a 202 carries no Retry-After
// (spec default: 5s).
DefaultPollInterval time.Duration
// MaxPolls bounds the polling loop as a safety valve. 0 = unbounded
// (the context deadline is then the only limit).
MaxPolls int
// Sign re-signs each polling GET. Deferred polling of a signed endpoint
// keeps presenting the agent's identity; leave nil for unsigned polls.
Sign func(*http.Request) error
// OnRequirement is invoked once per distinct AAuth-Requirement header
// seen on a 202 (e.g. requirement=interaction; url=…; code=…) so the
// caller can surface the interaction to the user while polling continues.
OnRequirement func(Requirement)
// HandleRequirement, when set, is invoked like OnRequirement (once per
// distinct AAuth-Requirement on a 202, before the next poll) for a
// requirement the caller satisfies itself — for example an auth-token
// requirement delivered as a deferred response (draft -11 §6.5.1),
// after which Sign presents the auth token on the polls. An error
// stops polling and is returned.
HandleRequirement func(Requirement) error
// OnClarification answers a requirement=clarification 202 (§7.3): given
// the question, it returns the agent's reply. If nil, a clarification
// requirement is treated as an ordinary pending state (polling continues
// without answering — which will eventually time out server-side).
OnClarification func(Clarification) (ClarificationReply, error)
}
DeferredOptions tunes DoDeferred.
type InteractionRequest ¶
type InteractionRequest struct {
// Type is InteractionTypeInteraction, InteractionTypePayment, or
// InteractionTypeQuestion. REQUIRED.
Type string `json:"type"`
// Description is Markdown context for the user.
Description string `json:"description,omitempty"`
// URL and Code are the interaction URL and code to relay (interaction
// and payment types).
URL string `json:"url,omitempty"`
Code string `json:"code,omitempty"`
// MaxWait is the most seconds the PS SHOULD hold the relay's deferred
// response before resolving it (interaction and payment types).
MaxWait int `json:"max_wait,omitempty"`
// Question is the Markdown question for the user (question type).
Question string `json:"question,omitempty"`
// MissionS256 names the mission the request belongs to.
MissionS256 string `json:"mission_s256,omitempty"`
}
InteractionRequest is the body of POST {interaction_endpoint} (draft -11 §7.6.1): the agent's channel to the user through the PS, with or without a mission.
type InteractionResponse ¶
type InteractionResponse struct {
Answer string `json:"answer,omitempty"`
}
InteractionResponse is the terminal 200 body of the interaction endpoint (§7.6.2): for the question type, the user's answer.
type JWK ¶
type JWK struct {
Kty string `json:"kty"` // key type; "OKP" (Ed25519) or "EC" (P-256)
Crv string `json:"crv,omitempty"` // curve; "Ed25519" or "P-256"
X string `json:"x,omitempty"` // base64url public key (OKP) or x coordinate (EC)
Y string `json:"y,omitempty"` // base64url y coordinate (EC only)
Kid string `json:"kid,omitempty"` // key id; the RFC 7638 thumbprint
Alg string `json:"alg,omitempty"` // fully-specified algorithm; "Ed25519" or "ES256"
Use string `json:"use,omitempty"` // intended use; "sig"
}
JWK is a minimal JSON Web Key. Ed25519 OKP keys are the AAuth baseline (draft -11 §11.3.1: Ed25519 MUST; ES256 SHOULD). Every key AAuth conveys or references MUST carry a fully-specified alg (signature-key §3.3); JWK.Validate enforces this.
func NewEd25519JWK ¶
NewEd25519JWK builds a JWK from an Ed25519 public key with Kid set to the RFC 7638 thumbprint.
func NewJWK ¶
NewJWK builds the public JWK for pub (an ed25519.PublicKey or a P-256 *ecdsa.PublicKey) with its fully-specified alg, use "sig", and Kid set to the RFC 7638 thumbprint.
func (JWK) PublicKey ¶
PublicKey validates the JWK (see JWK.Validate) and decodes it to a crypto.PublicKey: an ed25519.PublicKey for alg Ed25519, or a P-256 *ecdsa.PublicKey for alg ES256. Decoding failures, including an EC point not on the curve, are ErrInvalidKey.
func (JWK) Thumbprint ¶
Thumbprint computes the RFC 7638 thumbprint over the required members in lexicographic order with no whitespace — crv, kty, x for OKP (RFC 8037 §2); crv, kty, x, y for EC (RFC 7638 §3.2) — base64url-encoded.
func (JWK) Validate ¶
Validate applies the Algorithm Determination rules (signature-key §3.3; draft -11 §11.3.1): alg MUST be present and fully specified — the polymorphic "EdDSA", "none", and symmetric algorithms are rejected with ErrUnsupportedAlgorithm — and kty/crv MUST agree with alg, else ErrInvalidKey.
type JWKS ¶
type JWKS struct {
Keys []JWK `json:"keys"` // the key set
}
JWKS is a JSON Web Key Set, served at the URL published as jwks_uri in a well-known metadata document.
type JWKSCache ¶
type JWKSCache struct {
// MinRefresh is the minimum interval between fetches for one issuer
// (default DefaultJWKSMinRefresh).
MinRefresh time.Duration
// MaxAge is the age at which a cached set is discarded (default
// DefaultJWKSMaxAge).
MaxAge time.Duration
// TTL is the freshness lifetime when a response has no cache headers
// (default DefaultJWKSTTL).
TTL time.Duration
// MaxEntries bounds the number of cached issuers (default
// DefaultJWKSMaxEntries); the least recently fetched entry is evicted.
MaxEntries int
// Now returns the current time; nil means time.Now.
Now func() time.Time
// contains filtered or unexported fields
}
JWKSCache caches issuer key sets, keyed by (iss, dwk), per draft -11 §11.4:
- responses are cached, honoring Cache-Control max-age / no-cache / no-store and Expires, else for TTL;
- an unknown kid refreshes the set, but no issuer is fetched more than once per MinRefresh;
- on a failed fetch the cached set keeps serving, and retries back off exponentially;
- a set older than MaxAge is discarded regardless of cache headers.
The zero value is ready to use. A JWKSCache is safe for concurrent use.
type JWKSResolver ¶
type JWKSResolver struct {
// HTTPClient performs discovery fetches; nil uses [DiscoveryClient],
// which reaches only public https destinations. A client supplied here
// SHOULD apply the same egress admission (signature-key §7.3).
HTTPClient *http.Client
// Cache holds fetched key sets; nil uses DefaultJWKSCache.
Cache *JWKSCache
}
JWKSResolver verifies token signatures via the signature-key §3.6 discovery chain: {iss}/.well-known/{dwk} → jwks_uri → key by kid. The metadata document's issuer must equal iss (see FetchMetadata), and key sets are cached (see JWKSCache).
func NewJWKSResolver ¶
func NewJWKSResolver(hc *http.Client) JWKSResolver
NewJWKSResolver returns a JWKSResolver with its own cache.
func (JWKSResolver) RefreshKey ¶
func (r JWKSResolver) RefreshKey(ctx context.Context, iss, dwk, kid string, _ *JWK) (crypto.PublicKey, error)
RefreshKey implements KeyRefresher: it refetches the issuer's key set (unless fetched within the refresh floor) and returns the key for kid.
func (JWKSResolver) ResolveKey ¶
func (r JWKSResolver) ResolveKey(ctx context.Context, iss, dwk, kid string, _ *JWK) (crypto.PublicKey, error)
ResolveKey implements KeyResolver, serving from the cache when it is fresh and holds kid, and fetching otherwise (an unknown kid triggers a refresh, subject to the once-per-minute floor).
type KeyRefresher ¶
type KeyRefresher interface {
RefreshKey(ctx context.Context, iss, dwk, kid string, cnf *JWK) (crypto.PublicKey, error)
}
KeyRefresher is implemented by key resolvers that cache keys. When a token signature fails to verify under a cached key, the verifiers in this package call RefreshKey once and retry (draft -11 §11.4: silent re-keying under the same kid), subject to the resolver's own fetch limits.
type KeyResolver ¶
type KeyResolver interface {
ResolveKey(ctx context.Context, iss, dwk, kid string, cnf *JWK) (crypto.PublicKey, error)
}
KeyResolver resolves the token-signature verification key for an issuer.
Deployments choose the trust model (signature-key draft §3.6 step 5):
- JWKSResolver: fetch {iss}/.well-known/{dwk} → jwks_uri → key by kid.
- StaticResolver: pre-configured issuer keys (air-gapped / pinned).
- SelfSignedResolver: verify against the token's own cnf.jwk — the self-hosted/local shape where possession of the cnf key IS the identity and the verifier applies its own policy per agent.
ResolveKey returns the issuer's public key as a crypto.PublicKey (ed25519.PublicKey or P-256 *ecdsa.PublicKey); keys decoded from a JWK have already passed JWK.Validate.
type Limiter ¶
type Limiter interface {
Allow(ctx context.Context, key string) (ok bool, retryAfter time.Duration)
}
Limiter rate-limits actions a server performs on behalf of callers, identified by key. The role servers (personserver, accessserver, agentprovider) consult it where the protocol defines what a refusal means — interaction code attempts (draft -11 §11.6.3.1), polling (slow_down, §11.9.4), revocations (rate_limited, §11.12.3), and the distinct resources an agent requests person tokens for (§7.1) — and answer with that protocol response. Keys are namespaced by the caller ("poll:", "revoke:", ...).
Allow reports whether the action may proceed now and, when it may not, how long the caller should wait. Implementations must be safe for concurrent use. The ratelimit package provides an in-memory token bucket for single-instance deployments; a multi-instance deployment plugs in a shared one. Generic per-IP or per-route limiting belongs in the hosting application or a gateway.
type LimiterFunc ¶
LimiterFunc adapts a function to Limiter.
type MissingComponentsError ¶
type MissingComponentsError struct {
Required []string // every component the verifier requires
Missing []string // the required components the signature omitted
}
MissingComponentsError reports a Signature-Input that does not cover the components the verifier requires (Signature-Error invalid_input with required_input). It matches ErrSignatureInput under errors.Is.
func (*MissingComponentsError) Error ¶
func (e *MissingComponentsError) Error() string
Error implements error.
func (*MissingComponentsError) Is ¶
func (e *MissingComponentsError) Is(target error) bool
Is reports whether target is ErrSignatureInput.
type Mission ¶
type Mission struct {
// Agent is the agent identifier the mission belongs to. REQUIRED.
Agent string `json:"agent"`
// ApprovedAt is the ISO 8601 approval time; it makes s256 globally
// unique. REQUIRED.
ApprovedAt string `json:"approved_at"`
// ExpiresAt is the ISO 8601 time after which the PS treats the mission
// as terminated. Optional; it caps person and auth tokens.
ExpiresAt string `json:"expires_at,omitempty"`
// Description is the approved Markdown scope, which MAY differ from the
// proposal. REQUIRED.
Description string `json:"description"`
// ApprovedTools may be used without per-call permission (§7.7).
ApprovedTools []MissionTool `json:"approved_tools,omitempty"`
// ApprovedResources records the resources pre-approved from the
// proposal; not a limit.
ApprovedResources []string `json:"approved_resources,omitempty"`
}
Mission is the decoded mission blob (draft -11 §8.2). The members are a floor, not a closed set: a reader ignores members it does not recognize, and the identifier covers the exact bytes, so always keep the blob itself (see ApprovedMission).
type MissionAction ¶
type MissionAction struct {
Action string `json:"action"`
Description string `json:"description,omitempty"` // what changed (update)
Summary string `json:"summary,omitempty"` // what was accomplished (completion)
}
MissionAction is the body of POST {mission_endpoint}/{mission_s256} (draft -11 §8.4, §8.5): action is REQUIRED; update carries description and completion carries summary, each REQUIRED Markdown.
func (MissionAction) Validate ¶
func (a MissionAction) Validate() error
Validate checks the action member and the member it requires; a PS answers a failure with 400 invalid_request (§8, §8.7).
type MissionApproval ¶
type MissionApproval struct {
// S256 is the mission identifier: the unpadded base64url SHA-256 of
// the bytes Mission decodes to. REQUIRED.
S256 string `json:"s256"`
// Mission is the mission blob, base64url without padding. REQUIRED.
Mission string `json:"mission"`
// Capabilities the PS can currently provide on the person's behalf;
// the agent unions them with its own (§11.7). Not part of the blob.
Capabilities []string `json:"capabilities,omitempty"`
// PersonTokens maps each approved resource to a person token carrying
// mission_s256 = S256.
PersonTokens map[string]string `json:"person_tokens,omitempty"`
}
MissionApproval is the 200 body of an approved proposal (draft -11 §8.2): the mission blob, base64url-encoded without padding, its s256, and the PS's capabilities and person tokens.
func NewMissionApproval ¶
func NewMissionApproval(blob []byte, capabilities []string, personTokens map[string]string) MissionApproval
NewMissionApproval builds an approval response (PS side, draft -11 §8.2) for blob, the exact bytes the PS persists: s256 is computed over them and they are returned, base64url-encoded, as mission.
func (*MissionApproval) Decode ¶
func (a *MissionApproval) Decode() (*Mission, []byte, error)
Decode decodes the mission blob, verifies it against S256 (§8.2: an agent SHOULD before first use), and parses it, requiring agent, approved_at, and description. It returns the parsed mission and the exact blob bytes.
type MissionProposal ¶
type MissionProposal struct {
// Description is Markdown describing what the agent intends to
// accomplish. REQUIRED.
Description string `json:"description"`
// Tools the agent wants to use.
Tools []MissionTool `json:"tools,omitempty"`
// Resources the agent expects to access, as server identifiers; the PS
// issues a person token for each it approves. Not a limit on the
// mission.
Resources []string `json:"resources,omitempty"`
}
MissionProposal is the body of POST {mission_endpoint} (draft -11 §8.1).
type MissionStatusError ¶
type MissionStatusError struct {
Code string `json:"error"` // mission_terminated
MissionStatus string `json:"mission_status"` // terminated
TerminationReason string `json:"termination_reason,omitempty"` // a Termination* value, optional
}
MissionStatusError is the error a PS returns, 403 with a problem body, when a request names a mission (by mission_s256) that is no longer active (draft -11 §8.8). The agent MUST stop acting on the mission; it reads TerminationReason for context — expired invites proposing a new mission, revoked does not.
func (*MissionStatusError) Error ¶
func (e *MissionStatusError) Error() string
Error implements the error interface.
type MissionTool ¶
type MissionTool struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
}
MissionTool is a tool in a mission proposal or approval (§8.1, §8.2).
type MissionUpdateResponse ¶
type MissionUpdateResponse struct {
S256 string `json:"s256"`
}
MissionUpdateResponse is the body acknowledging an accepted update (§8.4): the s256 of the update's bytes as the PS persists them, so the sequence of accepted updates is verifiable.
type PSClient ¶
type PSClient struct {
// BaseURL of the PS, e.g. "https://ps.example". It is the PS's issuer
// when Discover has not replaced it.
BaseURL string
// AuthTokenEndpoint is the auth token endpoint (§7.2); default
// BaseURL+"/token". Earlier drafts called it the token endpoint.
AuthTokenEndpoint string
// PersonTokenEndpoint is the person token endpoint (§7.1); default
// BaseURL+"/person".
PersonTokenEndpoint string
// PermissionEndpoint is the permission endpoint (§7.7); default
// BaseURL+"/permission".
PermissionEndpoint string
// AuditEndpoint is the audit endpoint (§7.8); default BaseURL+"/audit".
AuditEndpoint string
// InteractionEndpoint is the interaction endpoint (§7.6); default
// BaseURL+"/interaction".
InteractionEndpoint string
// MissionEndpoint is the mission endpoint (§8); default
// BaseURL+"/mission".
MissionEndpoint string
// Agent is the identity this client acts as. Required.
Agent *Agent
// HTTPClient makes requests; nil uses [EgressClient], which reaches only
// public https destinations. The PS may be named by an upstream token
// and advertises its own endpoints, so a client that reaches internal
// addresses is an explicit opt-in: set HTTPClient (for example to
// http.DefaultClient in local development).
HTTPClient *http.Client
// PreferWaitSeconds sets `Prefer: wait=N` on requests that may defer.
PreferWaitSeconds int
// OnRequirement is invoked when a deferred (202) response carries an
// AAuth-Requirement — e.g. requirement=interaction with the URL and code
// the user must visit. The agent surfaces it; polling continues.
OnRequirement func(Requirement)
// OnClarification answers a requirement=clarification 202 (§7.5) that
// arrives during a deferred request. Nil leaves clarifications
// unanswered (the request eventually times out server-side).
OnClarification func(Clarification) (ClarificationReply, error)
// RefreshMargin is how long before its exp a cached token is replaced
// rather than presented (draft -11 §7.9.1); zero means
// DefaultRefreshMargin.
RefreshMargin time.Duration
// contains filtered or unexported fields
}
PSClient calls a Person Server on behalf of an agent (draft -11 §7). Every PS endpoint authenticates the agent by an HTTP message signature with its agent token in Signature-Key (§11.3).
Endpoint URLs come from the PS's metadata (§11.2.2) after PSClient.Discover, or are set directly; when neither, each defaults to a path under BaseURL.
func NewPSClient ¶
NewPSClient returns a client with sane defaults. Its HTTPClient is nil, so requests go through EgressClient; see PSClient.HTTPClient.
func (*PSClient) Audit ¶
func (c *PSClient) Audit(ctx context.Context, a AuditRequest) error
Audit posts an action record to the PS audit endpoint (§7.8). Returns nil on 201 Created; a *MissionStatusError when the mission is no longer active (§8.8; the caller MUST stop acting on it).
The endpoint is PersonServerMetadata.AuditEndpoint when discovered, else BaseURL+"/audit".
func (*PSClient) CompleteMission ¶
CompleteMission proposes that the mission is finished (draft -11 §8.5), following the deferred response while the person reviews the summary. A nil error means the person accepted and the PS terminated the mission as completed; follow-up questions arrive as clarifications.
func (*PSClient) Discover ¶
func (c *PSClient) Discover(ctx context.Context) (*PersonServerMetadata, error)
Discover fetches the PS's metadata from {BaseURL}/.well-known/aauth-person.json (draft -11 §11.2.2), verifies its issuer and REQUIRED members, and fills every endpoint not already set.
func (*PSClient) ForgetPersonTokens ¶
ForgetPersonTokens drops every cached person token for resource, so the next PSClient.PersonToken requests a fresh one — for example after the resource refused a cached token as expired or revoked.
func (*PSClient) Issuer ¶
Issuer is the PS's identifier: the discovered issuer, else BaseURL. It is the iss of the person tokens and the ps of the resource and auth tokens this PS is named in.
func (*PSClient) Metadata ¶
func (c *PSClient) Metadata() *PersonServerMetadata
Metadata returns the document Discover fetched, or nil.
func (*PSClient) PersonToken ¶
PersonToken returns a person token for req, from the cache when one is held that is more than RefreshMargin from its exp, else from PSClient.RequestPersonToken (draft -11 §7.1: an agent SHOULD cache a person token for a resource until it expires rather than requesting one per call). Tokens are cached per bound key, resource, mission, and upstream token: a person token is scoped to one resource and, when it carries mission_s256, one mission, and rotating the signing key invalidates all of them. The hints in req do not distinguish entries.
func (*PSClient) ProposeMission ¶
func (c *PSClient) ProposeMission(ctx context.Context, p MissionProposal) (*ApprovedMission, error)
ProposeMission proposes a mission at the PS's mission endpoint (draft -11 §8.1) and follows the deferred response — review, clarification chat via OnClarification — to the approval (§8.2). The approval is verified before it is returned: the blob hashes to s256, names this agent, and carries its REQUIRED members; each person token is for the resource it is listed under, binds the agent's key, and carries mission_s256. The person tokens are added to the person token cache.
The endpoint is PSClient.MissionEndpoint, else BaseURL+"/mission".
func (*PSClient) RelayInteraction ¶
func (c *PSClient) RelayInteraction(ctx context.Context, r Requirement, description, missionS256 string) (*InteractionResponse, error)
RelayInteraction relays a resource's requirement=interaction (draft -11 §11.6.3.2) to the user through the PS: an interaction request carrying the requirement's URL and code. On ErrInteractionUnavailable the agent directs the user to {url}?code={code} itself. Either way the resource's pending URL remains authoritative for completion (§7.6.2).
func (*PSClient) RequestAuthToken ¶
func (c *PSClient) RequestAuthToken(ctx context.Context, treq AuthTokenRequest) (*AuthTokenResponse, error)
RequestAuthToken presents a resource token, with the token the agent presented to the resource, at the PS's auth token endpoint (draft -11 §7.2) and returns the granted auth token, following deferred (202) responses — including user interaction and clarification — until resolution. Errors from the PS are [*TokenError]s (§11.9.3), or a *MissionStatusError when the request's mission is no longer active (§8.8). Check the granted token with VerifyAuthTokenResponse before presenting it (§9.4.4); Transport does.
The endpoint is PSClient.AuthTokenEndpoint (from PersonServerMetadata.AuthTokenEndpoint when discovered), else BaseURL+"/token".
func (*PSClient) RequestInteraction ¶
func (c *PSClient) RequestInteraction(ctx context.Context, ir InteractionRequest) (*InteractionResponse, error)
RequestInteraction posts to the PS's interaction endpoint (draft -11 §7.6) and follows its deferred response to a terminal one:
- question: the PS asks the user and returns the answer.
- interaction, payment: the PS relays the URL and code to the user. When the URL is hosted by the PS itself, its terminal response is authoritative. When it is a resource's, the resource's pending URL is: the relay resolves once the user has engaged (or MaxWait elapses), and the agent keeps polling the resource (§7.6.2).
An error matching ErrInteractionUnavailable (424) means the PS has no channel to the user; the agent directs the user itself (§11.6.3.2). user_unreachable (§11.9.3) and the polling errors (§11.9.4) are terminal [*ProblemError]s; a terminated mission is a *MissionStatusError (§8.8).
The endpoint is PSClient.InteractionEndpoint, else BaseURL+"/interaction".
func (*PSClient) RequestPermission ¶
func (c *PSClient) RequestPermission(ctx context.Context, p PermissionRequest) (*PermissionResponse, error)
RequestPermission performs the full ceremony (draft -11 §7.7, §11.8): mint token → attach Signature-Key → sign → POST → follow any deferred (202) responses until a terminal PermissionResponse arrives. A mission that is no longer active is a *MissionStatusError (§8.8); the agent MUST stop acting on it.
func (*PSClient) RequestPersonToken ¶
func (c *PSClient) RequestPersonToken(ctx context.Context, req PersonTokenRequest) (*PersonTokenResponse, error)
RequestPersonToken obtains a person token from the PS's person token endpoint (draft -11 §7.1), following deferred (202) responses — the PS may need to ask the person whether this agent may act at the resource as them — until resolution. It does not consult the cache; see PSClient.PersonToken.
The response is checked before it is returned: a person token, issued by this PS when its metadata was discovered, for req.Resource, and bound to the agent's key (the sub-agent's with SubagentToken). Its signature is not verified: the agent trusts its PS, and the resource verifies it.
The endpoint is PSClient.PersonTokenEndpoint, else BaseURL+"/person". Errors from the PS are [*TokenError]s, or a *MissionStatusError when req.MissionS256 names a mission that is no longer active (§8.8).
func (*PSClient) UpdateMission ¶
func (c *PSClient) UpdateMission(ctx context.Context, missionS256, description string) (*MissionUpdateResponse, error)
UpdateMission records a change in the work at the mission's URL (draft -11 §8.4) and returns the accepted update's s256. The PS may accept it at once or defer while the person reviews it. The mission itself — its blob and mission_s256 — is unchanged.
type PendingStatus ¶
type PendingStatus struct {
// Status is "pending", or "interacting" once the user has arrived at an
// interaction endpoint. Unrecognized values MUST be treated as pending.
Status string `json:"status"`
// Clarification, when present with requirement=clarification (§7.3.1),
// is a Markdown question the recipient must answer before proceeding.
Clarification string `json:"clarification,omitempty"`
// Timeout is the optional deadline (seconds) to answer a clarification.
Timeout int `json:"timeout,omitempty"`
// Options are discrete answer choices, when the question has them.
Options []string `json:"options,omitempty"`
}
PendingStatus is the body of a 202 pending response.
type PermissionRequest ¶
type PermissionRequest struct {
// Action identifies what the agent wants to do (e.g. a tool name).
// REQUIRED.
Action string `json:"action"`
// Description is an optional Markdown string: what and why.
Description string `json:"description,omitempty"`
// Parameters carries the arguments the agent intends to pass.
Parameters map[string]any `json:"parameters,omitempty"`
// MissionS256 names the mission the request belongs to (§8.2.1); the
// PS evaluates the request against the mission and its log.
MissionS256 string `json:"mission_s256,omitempty"`
}
PermissionRequest is the body of POST {permission_endpoint} (draft -11 §7.7.1) — governance for actions no remote resource governs: tool calls, file writes, messages sent on the person's behalf.
type PermissionResponse ¶
type PermissionResponse struct {
Permission string `json:"permission"` // "granted" or "denied"
// Reason optionally explains a denial (Markdown).
Reason string `json:"reason,omitempty"`
}
PermissionResponse is the 200 body of the permission endpoint (draft -11 §7.7.2). Denial is a 200 with permission="denied" — not an HTTP error.
func (*PermissionResponse) Granted ¶
func (r *PermissionResponse) Granted() bool
Granted reports whether the agent may proceed.
type PersonClaims ¶
type PersonClaims struct {
DWK string `json:"dwk"` // "aauth-person.json"
Cnf Cnf `json:"cnf"` // the agent's key
MissionS256 string `json:"mission_s256,omitempty"` // the mission the agent operates under (§8)
Tenant string `json:"tenant,omitempty"` // the person's organization (OpenID Enterprise)
// RegisteredClaims carries iss, sub, aud, jti, iat, exp.
jwt.RegisteredClaims
// contains filtered or unexported fields
}
PersonClaims is the payload of an aa-person+jwt (draft -11 §7.1.2).
Required: iss (the PS), dwk ("aauth-person.json"), aud (the one resource), sub (the person's directed identifier at that resource, the same value the PS uses in auth tokens for it), jti, iat, exp, cnf.jwk (the agent's key). Optional: mission_s256, tenant. A person token MUST NOT contain scope or account.
The person's identifier at a resource is the pair (iss, sub): sub is unique within the issuer only and is opaque (§7.1.4).
func IssuePersonToken ¶
func IssuePersonToken(p PersonTokenParams, key crypto.Signer, kid string) (string, *PersonClaims, error)
IssuePersonToken builds and signs a person token (draft -11 §7.1.2): iss, dwk aauth-person.json, aud = the resource, the directed sub, the bound key, a fresh jti, iat, and an exp no later than one hour, the agent token's exp, the upstream token's exp, and the mission's expires_at. It returns the token and its claims; a PS records jti, aud, and exp for revocation (§7.1).
func VerifyAndExtractPerson ¶
func VerifyAndExtractPerson(ctx context.Context, req *http.Request, resourceURL string, opts TokenVerifyOptions) (*PersonClaims, error)
VerifyAndExtractPerson authenticates a resource request signed with a person token in Signature-Key (draft -11 §7.1.3, §7.1.4): verify the token (VerifyPersonToken), then the HTTP message signature against its cnf.jwk. The person is identified by (claims.Issuer, claims.Subject).
func VerifyPersonToken ¶
func VerifyPersonToken(ctx context.Context, token, resourceURL string, opts TokenVerifyOptions) (*PersonClaims, error)
VerifyPersonToken verifies an aa-person+jwt per draft -11 §7.1.4 and the common JWT rules (§11.5.2) and returns its claims: typ aa-person+jwt, dwk aauth-person.json, the PS's signature via opts.Resolver, iss a server identifier, iat REQUIRED, a lifetime of at most one hour, aud equal to resourceURL (the verifier's own identifier), sub present, a structurally complete cnf.jwk, no scope or account claim, and exp in the future.
It does not compare cnf.jwk with the key that signed the request: use VerifyAndExtractPerson at a resource. A PS verifying a presented or upstream token passes the audience the substituted rule names (§6.7.2, §9.4.5).
func (*PersonClaims) PersonServer ¶
func (c *PersonClaims) PersonServer() string
PersonServer returns iss, the PS that issued the person token.
type PersonServerMetadata ¶
type PersonServerMetadata struct {
ServerMetadata
// AuthTokenEndpoint is where agents send auth token requests (§7.2).
// REQUIRED. Earlier drafts called it token_endpoint.
AuthTokenEndpoint string `json:"auth_token_endpoint"`
// PersonTokenEndpoint is where agents request a person token for a
// resource (§7.1). REQUIRED.
PersonTokenEndpoint string `json:"person_token_endpoint"`
// MissionEndpoint is where an agent proposes, updates, and completes
// the missions it owns (§8); a mission's own URL is
// {mission_endpoint}/{mission_s256}.
MissionEndpoint string `json:"mission_endpoint,omitempty"`
// PermissionEndpoint is where agents request permission for actions no
// remote resource governs (§7.7).
PermissionEndpoint string `json:"permission_endpoint,omitempty"`
// AuditEndpoint is where agents log actions performed (§7.8).
AuditEndpoint string `json:"audit_endpoint,omitempty"`
// InteractionEndpoint is where agents relay interactions to the user
// through the PS (§7.6).
InteractionEndpoint string `json:"interaction_endpoint,omitempty"`
// MissionControlEndpoint is the PS's mission control plane, for parties
// other than the owning agent (§8.6); defined by a companion
// specification.
MissionControlEndpoint string `json:"mission_control_endpoint,omitempty"`
// RevocationEndpoint is where an agent provider revokes an agent token
// it issued, and a resource a resource token this PS holds (§11.12).
// RECOMMENDED.
RevocationEndpoint string `json:"revocation_endpoint,omitempty"`
// ScopesSupported lists the scope values the PS supports, including
// identity and enterprise scopes. RECOMMENDED.
ScopesSupported []string `json:"scopes_supported,omitempty"`
// ClaimsSupported lists the identity claim names the PS can provide.
// RECOMMENDED.
ClaimsSupported []string `json:"claims_supported,omitempty"`
}
PersonServerMetadata is /.well-known/aauth-person.json (draft -11 §11.2.2). Its four REQUIRED members — issuer, jwks_uri, auth_token_endpoint, and person_token_endpoint — are the whole of what a conformant PS publishes; the OPTIONAL endpoints add missions, permission checks, audit, and the relay channel to the person.
func (PersonServerMetadata) Validate ¶
func (m PersonServerMetadata) Validate() error
Validate checks the members a PS MUST publish: issuer, jwks_uri, auth_token_endpoint, and person_token_endpoint.
type PersonTokenParams ¶
type PersonTokenParams struct {
// Issuer is the PS's server identifier (iss).
Issuer string
// Resource is the resource the token identifies the person to (aud).
// The PS MUST validate it as a server identifier (§7.1).
Resource string
// Subject is the person's directed identifier at Resource (sub),
// unique within Issuer (§14.1). It MUST NOT be copied from an upstream
// token (§10.1.1.2).
Subject string
// Agent is the verified agent token that signed the request. Its exp
// bounds the token's, and its key is bound unless Subagent is set.
Agent *AgentClaims
// Subagent, when a parent requests on behalf of a sub-agent, is the
// subagent_token verified with [VerifySubagentToken]; the issued
// token binds the sub-agent's key (§10.2.3).
Subagent *AgentClaims
// MissionS256 names the mission the token is issued under, after the
// PS has verified it exists, is active, and belongs to the agent (or
// was copied from the upstream token). MissionExpiresAt, when set, is
// the mission's expires_at, which bounds the token.
MissionS256 string
MissionExpiresAt time.Time
// UpstreamExpiresAt is the exp of the verified upstream_token in call
// chaining (§10.1.1), which bounds the token.
UpstreamExpiresAt time.Time
// Tenant is the person's organization, when known.
Tenant string
// TTL is the requested lifetime; zero or more than one hour means one
// hour (§7.1.2).
TTL time.Duration
// Now is the issuance time; zero means time.Now.
Now time.Time
// InsecureSkipIdentifierCheck accepts a Resource that is not a server
// identifier, for development and tests against local servers.
InsecureSkipIdentifierCheck bool
}
PersonTokenParams describes a person token a PS issues from its person_token_endpoint (draft -11 §7.1).
type PersonTokenRequest ¶
type PersonTokenRequest struct {
// Resource is the resource the person token is for, a server
// identifier (§11.1.1); it becomes the token's aud. REQUIRED.
Resource string `json:"resource"`
// MissionS256 names the mission the agent operates under (§8). Not
// sent with UpstreamToken, which carries the mission itself.
MissionS256 string `json:"mission_s256,omitempty"`
// SubagentToken is a sub-agent's agent token, when a parent obtains a
// person token on its behalf (§10.2.3); the issued token binds the
// sub-agent's key.
SubagentToken string `json:"subagent_token,omitempty"`
// UpstreamToken is the person or auth token the calling agent
// presented, when a resource acting as an agent needs a person token
// for a downstream resource (§10.1.1).
UpstreamToken string `json:"upstream_token,omitempty"`
TokenRequestHints
}
PersonTokenRequest is the body of POST {person_token_endpoint} (draft -11 §7.1).
type PersonTokenResponse ¶
type PersonTokenResponse struct {
PersonToken string `json:"person_token"` // the issued aa-person+jwt
ExpiresIn int64 `json:"expires_in"` // token lifetime in seconds
}
PersonTokenResponse is the 200 body of the person token endpoint (§7.1).
type PresentedToken ¶
type PresentedToken interface {
// PersonServer is the PS whose namespace the token's sub belongs to:
// the iss of a person token, the ps of an auth token.
PersonServer() string
// contains filtered or unexported methods
}
PresentedToken is a verified token that a request carried in Signature-Key and that a resource token names (draft -11 §6.7): a person token (*PersonClaims) or an auth token (*AuthClaims). It is also the form of a verified presented_token or upstream_token at a PS or AS (§6.7.2, §9.4.5). Only claims returned by this package's verification functions qualify for issuing a resource token; claims built or decoded by hand are refused.
func VerifyUpstreamToken ¶
func VerifyUpstreamToken(ctx context.Context, token string, opts UpstreamVerifyOptions) (PresentedToken, error)
VerifyUpstreamToken verifies an upstream_token parameter of a call chaining request (draft -11 §9.4.5) at a PS or AS:
- by typ, as a person token (§7.1.4) or auth token (§9.4.3), with aud equal to the intermediary's identifier, cnf.jwk not compared with the signing key (it is the calling agent's), and no record check;
- the issuer: a person token's iss, or an auth token's ps, is opts.PS, and at a PS an auth token's iss is the PS or a trusted AS;
- the upstream token's aud equals the intermediary's agent token iss.
Failures are [*TokenError]s: invalid_upstream_token, expired_upstream_token, revoked_upstream_token (when the resolver or a caller-side check reports ErrRevoked), or clock_skew. Steps 4 and 5 — identifying the calling agent from the PS's own records and evaluating the mission — are the caller's. The downstream issuer MUST NOT copy the upstream sub into anything it issues (§10.1.1.2).
type ProblemError ¶
type ProblemError struct {
Status int // the HTTP status
Code string // the error member, e.g. mission_not_found
Detail string // the detail member, if any
// RetryAfter is the response's Retry-After, when it carried one in
// seconds (REQUIRED with rate_limited).
RetryAfter time.Duration
}
ProblemError is an RFC 9457 error response (draft -11 §11.9.2) from an AAuth endpoint, other than a token endpoint error (*TokenError) or a mission status error (*MissionStatusError).
func (*ProblemError) Is ¶
func (e *ProblemError) Is(target error) bool
Is matches ErrInteractionUnavailable for code interaction_unavailable and ErrRevocationUnsupported for unsupported_iss.
type RequestVerifyOptions ¶
type RequestVerifyOptions struct {
// Window is the signature validity window for created. A signature
// older than the window is rejected as invalid ([ErrSignatureInvalid]);
// one further ahead of the verifier's clock than the window is rejected
// as clock skew ([ErrClockSkew]). Zero means DefaultSignatureWindow.
Window time.Duration
// RequiredComponents lists covered components the server requires in
// addition to the base set (e.g. a resource's
// additional_signature_components).
RequiredComponents []string
// RequireBodyCoverage requires content-digest and content-type to be
// covered on a request with a body. PS, AS, and revocation endpoints set
// it (draft -11 §11.3.3.1).
RequireBodyCoverage bool
// Now returns the verifier's current time; nil means time.Now.
Now func() time.Time
}
RequestVerifyOptions tunes HTTP message-signature verification.
type Requirement ¶
type Requirement struct {
// Requirement is the requirement value, one of the Requirement*
// constants or an extension value.
Requirement string
// ResourceToken accompanies requirement=auth-token.
ResourceToken string
// URL accompanies requirement=interaction (where the user goes).
URL string
// Code accompanies requirement=interaction (shown to the user).
Code string
}
Requirement is a parsed AAuth-Requirement header value (draft -11 §11.6.1): a Structured Field Dictionary (RFC 9651 §3.2) whose requirement member is a Token, with requirement-specific data as parameters on that member, e.g.:
requirement=auth-token; resource-token="eyJ..." requirement=interaction; url="https://ps.example/interaction"; code="A1B2-C3D4" requirement=person-token
func ParseRequirement ¶
func ParseRequirement(v string) (Requirement, error)
ParseRequirement parses an AAuth-Requirement header value as a Structured Field Dictionary (draft -11 §11.6.1). The requirement member MUST be present and MUST be a Token. Its resource-token, url, and code parameters are read as Strings (Tokens are tolerated); unknown parameters and members are ignored (recipients MUST ignore unknown parameters).
func (Requirement) String ¶
func (r Requirement) String() string
String renders the header value: the requirement token followed by its parameters as quoted Strings, in the "; " style of the draft's examples (RFC 9651 permits the space after ";").
type ResourceChallengeOptions ¶
type ResourceChallengeOptions struct {
// TokenVerifyOptions verifies the resource token's signature: the
// resource's key, discovered at {iss}/.well-known/aauth-resource.json
// by a [JWKSResolver], or pinned.
TokenVerifyOptions
// Resource is the resource the agent sent the request to; the token's
// iss must equal it.
Resource string
// Agent is the agent; agent_jkt must be its key's thumbprint.
Agent *Agent
// PS is the agent's own person server; the token's ps must equal it.
PS string
// Presented is the token the agent presented on the request that was
// challenged (a person token or an auth token); the resource token's
// sub and presented_jti must match it.
Presented string
}
ResourceChallengeOptions tunes VerifyResourceChallenge.
type ResourceClaims ¶
type ResourceClaims struct {
DWK string `json:"dwk"` // "aauth-resource.json"
PS string `json:"ps"` // the PS the person is represented by
PresentedJTI string `json:"presented_jti"` // jti of the token the request presented
AgentJKT string `json:"agent_jkt"` // thumbprint of the agent's signing key
Scope string `json:"scope,omitempty"` // requested scopes, space-separated
Account string `json:"account,omitempty"` // the account at the resource (§11.11)
LoginHint string `json:"login_hint,omitempty"` // who the authorization is for; agents pass it unchanged
MissionS256 string `json:"mission_s256,omitempty"` // copied from the presented token
Tenant string `json:"tenant,omitempty"` // copied from the presented token
Interaction *ResourceInteraction `json:"interaction,omitempty"` // resource's own interaction requirement
// RegisteredClaims carries iss (the resource), aud (PS or AS), sub,
// jti, iat, exp.
jwt.RegisteredClaims
}
ResourceClaims is the payload of an aa-resource+jwt (draft -11 §6.7.1).
Required: iss (the resource), dwk ("aauth-resource.json"), aud (the PS in three-party, the AS in four-party), ps (the PS whose namespace sub belongs to), sub and presented_jti (copied from the token the request presented), agent_jkt (the agent's key thumbprint; a resource token carries no cnf), jti, iat, exp (SHOULD NOT exceed 5 minutes). Optional: scope, account, login_hint, mission_s256 (REQUIRED when the presented token carried one), tenant, interaction.
func VerifyResourceChallenge ¶
func VerifyResourceChallenge(ctx context.Context, token string, opts ResourceChallengeOptions) (*ResourceClaims, error)
VerifyResourceChallenge is the agent-side check of a requirement=auth-token challenge (draft -11 §6.7.3) before the agent sends the resource token to its PS: the resource token's signature verifies under the resource's key, its iss is the resource the agent called, agent_jkt is the agent's key, ps is the agent's own PS, sub and presented_jti match the token the agent presented, and exp is in the future. The agent then sends the resource token, with opts.Presented as presented_token, to its PS.
type ResourceInteraction ¶
type ResourceInteraction struct {
URL string `json:"url"` // the resource's interaction endpoint
Code string `json:"code"` // interaction code to present there
}
ResourceInteraction is the optional interaction claim in a resource token (§6.7.1): the resource requires its own user-facing flow before the PS can issue an auth token (§7.2.3).
type ResourceMetadata ¶
type ResourceMetadata struct {
ServerMetadata
// AccessMode is the declared access mode (AccessMode*). Advisory.
AccessMode string `json:"access_mode,omitempty"`
// AuthorizationEndpoint is where agents request authorization ahead of
// a challenge (§6.6).
AuthorizationEndpoint string `json:"authorization_endpoint,omitempty"`
// ScopeDescriptions maps scope values to Markdown for consent display
// (§11.10).
ScopeDescriptions map[string]string `json:"scope_descriptions,omitempty"`
// SignatureWindow is the signature validity window in seconds for the
// created timestamp (§11.3.4). Zero means the default, 60.
SignatureWindow int `json:"signature_window,omitempty"`
// AdditionalSignatureComponents are HTTP message components agents
// MUST cover when signing requests to this resource, beyond the base
// set (§11.3.3.1).
AdditionalSignatureComponents []string `json:"additional_signature_components,omitempty"`
// RevocationEndpoint is where the issuer of an auth token or person
// token for this resource revokes it (§11.12). RECOMMENDED for a
// resource that accepts person tokens.
RevocationEndpoint string `json:"revocation_endpoint,omitempty"`
}
ResourceMetadata is /.well-known/aauth-resource.json (draft -11 §11.2.4). AccessMode declares the credential flow the resource expects; see the AccessMode* constants and ResourceMetadata.EffectiveAccessMode. A resource that publishes none can still verify identity-based access and issue resource tokens and interaction requirements via 401 responses.
func (ResourceMetadata) EffectiveAccessMode ¶
func (m ResourceMetadata) EffectiveAccessMode() string
EffectiveAccessMode returns the declared access mode an agent plans with: AccessMode when it is a value this implementation recognizes, else AccessModeAgentToken — the default when none is declared, and how an agent proceeds when it does not recognize the value (§11.2.4).
func (ResourceMetadata) Validate ¶
func (m ResourceMetadata) Validate() error
Validate checks the member a resource MUST publish: issuer.
type ResourceTokenParams ¶
type ResourceTokenParams struct {
// Resource is the resource's own identifier (iss).
Resource string
// Audience is the party that will redeem the token: the resource's AS
// in four-party. Empty means three-party: the PS the presented token
// names (the iss of a person token, the ps of an auth token, §6.7).
Audience string
// Scope is the requested scope, space-separated (§11.10).
Scope string
// Account echoes the account parameter of the authorization request
// (§11.11).
Account string
// LoginHint, for a resource that knows who the authorization is for.
LoginHint string
// Interaction, when the resource requires its own user-facing flow
// before the PS can issue an auth token (§7.2.3).
Interaction *ResourceInteraction
// TTL is the requested lifetime; zero or more than five minutes means
// five minutes (§6.7.1).
TTL time.Duration
// Now is the issuance time; zero means time.Now.
Now time.Time
}
ResourceTokenParams describes a resource token a resource issues (draft -11 §6.6.2, §6.7).
type ResourceTokenVerifyOptions ¶
type ResourceTokenVerifyOptions struct {
// TokenVerifyOptions verifies the resource token (the resource's key)
// and, unless PresentedResolver is set, the presented token.
TokenVerifyOptions
// Audience is the recipient's own identifier: the PS in three-party,
// the AS in four-party (step 1).
Audience string
// PS is the person server the token's ps claim must name: at a PS its
// own identifier, at an AS the PS that sent the token request (step 3).
PS string
// AgentJKT is the RFC 7638 thumbprint of the key that signed the token
// request, or, for a parent-mediated sub-agent request, of the
// subagent_token's cnf.jwk (step 2).
AgentJKT string
// PresentedToken is the presented_token parameter of the token request
// (§7.2.1, §9.1.1). Required.
PresentedToken string
// PresentedResolver, when set, locates the presented token's issuer
// key instead of Resolver.
PresentedResolver KeyResolver
// CheckMission, when set and the resource token carries mission_s256,
// verifies that the mission is active and before its expires_at
// (step 4; a PS MUST). Its error is returned unchanged.
CheckMission func(ctx context.Context, missionS256 string) error
}
ResourceTokenVerifyOptions tunes VerifyResourceToken.
type RevocationClient ¶
type RevocationClient struct {
// Signer signs the revocation and its polls under the jwks_uri scheme
// (§11.3.2). Required.
Signer ServerSigner
// HTTPClient makes requests; nil uses [DiscoveryClient], which reaches
// only public https destinations: the endpoint comes from the
// recipient's metadata.
HTTPClient *http.Client
// PreferWaitSeconds, when positive, is sent as Prefer: wait=N: how
// long the caller will wait for the cascade before a 202.
PreferWaitSeconds int
}
RevocationClient revokes tokens a server issued, at another server's revocation endpoint (draft -11 §11.12).
func (*RevocationClient) Revoke ¶
func (c *RevocationClient) Revoke(ctx context.Context, endpoint string, req RevocationRequest) (*RevocationResponse, error)
Revoke posts req to endpoint and returns the terminal outcome, polling the pending URL with signed GETs under the same identity when the recipient answers 202 (§11.12.3). Failures are a *ProblemError — invalid_request, unsupported_iss (403; also matches ErrRevocationUnsupported), rate_limited (429, with RetryAfter), or server_error — or an error wrapping ErrRevocationUnavailable for a response body that is not a JSON object. RevocationOutcomeFor maps any result to the outcome a cascading caller reports.
type RevocationOutcome ¶
type RevocationOutcome struct {
Recipient string `json:"recipient"` // the downstream party's identifier
Error string `json:"error,omitempty"` // RevocationUnsupported or RevocationUnavailable
}
RevocationOutcome is one entry of a RevocationResponse's downstream array: the recipient revoked at, and an error when that revocation did not succeed.
func RevocationOutcomeFor ¶
func RevocationOutcomeFor(recipient string, err error) RevocationOutcome
RevocationOutcomeFor maps the result of revoking at recipient to the outcome a recipient reports downstream (§11.12.3): no error is success; no revocation_endpoint or unsupported_iss is revocation_unsupported; anything else — no response, a timeout, a 5xx, a malformed response, rate limiting — is revocation_unavailable.
type RevocationRequest ¶
type RevocationRequest struct {
// JTI names the token to revoke within the caller's namespace.
// REQUIRED.
JTI string `json:"jti"`
// Exp is the revoked token's own exp (seconds since the epoch), which
// bounds how long the recipient remembers the revocation. REQUIRED.
Exp int64 `json:"exp"`
}
RevocationRequest is the body of POST {revocation_endpoint} (draft -11 §11.12.1).
func ParseRevocationRequest ¶
func ParseRevocationRequest(r *http.Request) (*RevocationRequest, error)
ParseRevocationRequest decodes and validates a revocation request body (recipient side, §11.12.1). Failures are a *ProblemError invalid_request (400). The caller has already verified the signature with VerifyServerRequest (RequireBodyCoverage set): the verified caller's ID is the iss the revocation is keyed under.
func (RevocationRequest) Validate ¶
func (r RevocationRequest) Validate() error
Validate checks the REQUIRED members; a recipient answers a failure with 400 invalid_request.
type RevocationResponse ¶
type RevocationResponse struct {
Downstream []RevocationOutcome `json:"downstream,omitempty"`
}
RevocationResponse is the body of a 200 revocation response (§11.12.3). An AS reports the resources it revoked at to the PS; other recipients answer with an empty body, which decodes to the zero value.
type SelfSignedResolver ¶
type SelfSignedResolver struct{}
SelfSignedResolver trusts the embedded cnf.jwk to verify the token's own signature (proof of possession is then established by the HTTP message signature, which must use the same key). Suitable for local/self-hosted agents where the verifier's policy layer decides what the identity may do. It does not verify sub-agent tokens, which the parent's provider key signs over the sub-agent's own cnf key (§10.2.1): resolve the provider's key.
func (SelfSignedResolver) ResolveKey ¶
func (SelfSignedResolver) ResolveKey(_ context.Context, _, _, _ string, cnf *JWK) (crypto.PublicKey, error)
ResolveKey implements KeyResolver, returning the key from cnf.jwk.
type ServerCaller ¶
type ServerCaller struct {
// ID is the caller's identity: its issuer, the iss of every token it
// mints.
ID string
// DWK is the metadata document its key was discovered through, which
// names its role (e.g. aauth-person.json for a PS).
DWK string
// Kid is the key that signed the request.
Kid string
}
ServerCaller identifies the server that signed a request under the jwks_uri scheme.
func VerifyServerRequest ¶
func VerifyServerRequest(ctx context.Context, req *http.Request, opts VerifyServerOptions) (*ServerCaller, error)
VerifyServerRequest authenticates a request signed under the jwks_uri scheme (draft -11 §11.3.2; signature-key §3.6): it parses the Signature-Key member for the default label, requires the jwks_uri scheme with id, dwk, and kid, discovers the key, and verifies the HTTP message signature. Errors classify with SignatureErrorFor; a jwt (agent) or other scheme is unsupported_scheme with Accept-Signature-Scheme: jwks_uri.
type ServerMetadata ¶
type ServerMetadata struct {
// Issuer is the server's HTTPS URL (REQUIRED). It MUST equal the URL
// the document was fetched from minus /.well-known/{dwk}
// ([FetchMetadata] checks this) and is the iss of the JWTs the server
// issues.
Issuer string `json:"issuer"`
// JWKSURI is the URL of the server's JSON Web Key Set. REQUIRED for an
// agent provider, PS, and AS; for a resource only when it issues
// resource tokens or makes signed calls (§11.2.4).
JWKSURI string `json:"jwks_uri,omitempty"`
// AcceptSignatureAlgs lists the fully-specified JWS algorithms the
// server's verifier accepts, exactly the set, with the semantics of the
// Accept-Signature-Alg response header. One list per server.
AcceptSignatureAlgs []string `json:"accept_signature_algs,omitempty"`
// Name is a human-readable display name.
Name string `json:"name,omitempty"`
// Description is Markdown describing the server, for consent screens
// and dashboards. Implementations MUST sanitize it before rendering.
Description string `json:"description,omitempty"`
// LogoURI, LogoDarkURI, DocumentationURI, TOSURI, and PolicyURI are
// https URLs for the server's logo (and dark-background variant),
// developer documentation, terms of service, and privacy policy.
LogoURI string `json:"logo_uri,omitempty"`
LogoDarkURI string `json:"logo_dark_uri,omitempty"`
DocumentationURI string `json:"documentation_uri,omitempty"`
TOSURI string `json:"tos_uri,omitempty"`
PolicyURI string `json:"policy_uri,omitempty"`
}
ServerMetadata holds the members defined identically across all four metadata documents (draft -11 §11.2, Table 12). It is embedded in each role's document type, so its members appear at the top level of the JSON object. A reader ignores members it does not recognize.
type ServerSigner ¶
type ServerSigner struct {
// Issuer is the server's issuer (its server identifier, §11.1.1),
// sent as the id parameter.
Issuer string
// DWK is the server's metadata document name: WellKnownPerson,
// WellKnownAccess, WellKnownAgent, or WellKnownResource.
DWK string
// Kid identifies Key in the JWKS at the server's jwks_uri. That JWKS
// entry MUST carry a fully-specified alg (§11.4).
Kid string
// Key is the server's signing key.
Key crypto.Signer
}
ServerSigner signs requests a server makes in its own right.
func (ServerSigner) SignRequest ¶
func (s ServerSigner) SignRequest(req *http.Request) error
SignRequest attaches the jwks_uri Signature-Key and signs req (see the package-level SignRequest for covered components).
type SignatureError ¶
type SignatureError struct {
// Code is the required error member.
Code string
// RequiredInput accompanies invalid_input: the covered components the
// server requires (an Inner List of Strings).
RequiredInput []string
// Params carries any other members, each in its RFC 9651 serialized
// form (a token, a quoted string, an inner list, ...). Unknown members
// are preserved; recipients ignore what they don't know.
Params map[string]string
// AcceptSchemes, when set, overrides the Accept-Signature-Scheme list
// WriteSignatureError sends with unsupported_scheme (for example
// jwks_uri on an endpoint for server-signed requests). It is not part
// of the Signature-Error header.
AcceptSchemes []string
}
SignatureError is a parsed Signature-Error header: a Structured Field Dictionary (RFC 9651 §3.2) whose error member is a Token.
func ParseSignatureError ¶
func ParseSignatureError(v string) (*SignatureError, error)
ParseSignatureError parses a Signature-Error header value as a Structured Field Dictionary. Returns nil if the value is empty (no signature error present).
func SignatureErrorFor ¶
func SignatureErrorFor(err error) (e SignatureError, ok bool)
SignatureErrorFor classifies a verification error from this package as a Signature-Error (draft -11 §11.3.4; signature-key §5.4). ok is false when err is not a signature or credential failure (for example a network error fetching an issuer's keys), leaving the response to the caller.
func SignatureErrorFromResponse ¶
func SignatureErrorFromResponse(res *http.Response) (*SignatureError, error)
SignatureErrorFromResponse extracts the Signature-Error from a response, or nil when absent.
func (SignatureError) String ¶
func (e SignatureError) String() string
String renders the header value as a Structured Field Dictionary: `error=<code>[, required_input=("..." ...)][, k=v ...]`, with any extra members in key order.
type SignatureKey ¶
type SignatureKey struct {
Label string // the dictionary key; matches the Signature-Input label
Scheme string // the scheme token, e.g. "jwt" or "jwks_uri"
Params map[string]string // String-valued parameters (jwt; id, dwk, kid)
}
SignatureKey is one parsed member of the Signature-Key header (signature-key §3): a Structured Field Dictionary keyed by signature label whose member value is a Token naming the scheme, with parameters.
func ParseSignatureKeyMember ¶
func ParseSignatureKeyMember(req *http.Request, label string) (SignatureKey, error)
ParseSignatureKeyMember parses the Signature-Key header as a Structured Field Dictionary (RFC 9651 §3.2) and returns the member for label. A missing header is ErrMissingSigKey; a malformed header, a missing member for the label, or a member that is not a Token with parameters is ErrBadSigKey. Members for other labels are ignored (§3.1), whatever their scheme.
type StaticResolver ¶
StaticResolver resolves issuers from a fixed map of iss → JWKS.
func (StaticResolver) ResolveKey ¶
func (r StaticResolver) ResolveKey(_ context.Context, iss, _, kid string, _ *JWK) (crypto.PublicKey, error)
ResolveKey implements KeyResolver, returning the pinned key for iss by kid.
type TokenError ¶
type TokenError struct {
Code string // the §11.9.3 error code
Err error // the underlying failure, if any
}
TokenError is a token endpoint error (draft -11 §11.9.3): Code is the error member of the problem body, and Err, when set, is the underlying cause, so errors.Is matches the package's sentinels (for example ErrExpired under expired_presented_token).
func NewTokenParamError ¶
func NewTokenParamError(param string, err error) *TokenError
NewTokenParamError classifies the verification failure err of the token carried as request parameter param (one of the Param* constants) per draft -11 §11.9.3: ErrRevoked → revoked_<param>, ErrExpired → expired_<param>, ErrClockSkew → clock_skew, a token or key failure → invalid_<param>, and anything else (for example a network error fetching the issuer's keys) → server_error. An err that is already a *TokenError is returned unchanged; a nil err returns nil.
func (*TokenError) Status ¶
func (e *TokenError) Status() int
Status is the HTTP status the code is answered with (§11.9.3).
func (*TokenError) Unwrap ¶
func (e *TokenError) Unwrap() error
Unwrap returns the underlying failure.
type TokenRequestHints ¶
type TokenRequestHints struct {
// Justification is Markdown declaring why access is requested; the PS
// presents it as agent-asserted content and asks clarification
// questions about it (§7.5).
Justification string `json:"justification,omitempty"`
// LoginHint is a hint about who to authorize ([OpenID.Core]). When a
// resource token carries login_hint, the agent sends it unchanged.
LoginHint string `json:"login_hint,omitempty"`
// Tenant and DomainHint follow OpenID Connect Enterprise Extensions.
Tenant string `json:"tenant,omitempty"`
DomainHint string `json:"domain_hint,omitempty"`
// Prompt is a space-delimited list of none, login, consent, and
// select_account.
Prompt string `json:"prompt,omitempty"`
// Platform is the agent's runtime platform, from the AAuth Platform
// Value registry (agent-attested).
Platform string `json:"platform,omitempty"`
// Device is a short printable label for the user's dashboard, at most
// 64 characters (agent-attested).
Device string `json:"device,omitempty"`
// Capabilities are the capability values (§11.7) the agent can handle
// for this request — the body form of AAuth-Capabilities, which is not
// used on PS endpoints. Without interaction, a PS that must reach the
// person answers user_unreachable.
Capabilities []string `json:"capabilities,omitempty"`
}
TokenRequestHints are the OPTIONAL parameters shared by the person token request (draft -11 §7.1) and the auth token request (§7.2.1), with the same definitions in both. They are embedded in PersonTokenRequest, so they appear at the top level of the JSON body.
type TokenVerifyOptions ¶
type TokenVerifyOptions struct {
// Resolver locates the issuer's token-signing key. Required.
Resolver KeyResolver
// Signature tunes HTTP message-signature verification in the
// VerifyAndExtract helpers. Its clock (Now) and validity window
// (Window) also bound how far a token's iat may be ahead of the
// verifier's clock before it is refused as clock skew (§11.5.2).
Signature RequestVerifyOptions
// InsecureSkipIdentifierCheck accepts an iss (and ps) that is not a
// server identifier (§11.1.1: https, host only, lowercase), for
// development and tests against local servers. Never set it in
// production.
InsecureSkipIdentifierCheck bool
}
TokenVerifyOptions tunes verification of tokens issued by servers — person, resource, and auth tokens (draft -11 §11.5.2).
type Transport ¶
type Transport struct {
// Agent is the identity every request is signed as. Required.
Agent *Agent
// PS obtains person tokens and auth tokens. Optional: without it,
// person-token and auth-token requirements fail with an error
// (identity-only deployments; an agent with no PS surfaces the
// requirement as an error, §6.4).
PS *PSClient
// MissionS256, when set, is the mission person tokens are requested
// under (§7.1, §8); it flows into resource tokens and auth tokens. It
// is not sent with UpstreamToken, which carries the mission itself.
MissionS256 string
// UpstreamToken, for an intermediary in call chaining (§10.1.1), is the
// person or auth token the calling agent presented to it. It is sent
// as upstream_token in the person and auth token requests to PS — the
// person server the upstream token names — so the tokens are issued
// for that person. UpstreamExpiresAt is its exp: no downstream token
// outlives it, and once it passes the intermediary needs a later token
// from the calling agent. See [ChainRouter.Transport].
UpstreamToken string
UpstreamExpiresAt time.Time
// Base is the underlying RoundTripper (default http.DefaultTransport).
Base http.RoundTripper
// ResourceVerify verifies the signature of resource tokens in
// auth-token challenges (§6.7.3). A nil Resolver uses a
// [JWKSResolver], which discovers the resource's key at
// {iss}/.well-known/aauth-resource.json.
ResourceVerify TokenVerifyOptions
// AuthVerify, when its Resolver is set, also verifies the signature of
// auth tokens the PS returns (§9.4.4 step 1, SHOULD). A nil Resolver
// runs the claim checks only: the agent trusts its PS.
AuthVerify TokenVerifyOptions
// OnRequirement surfaces interaction requirements (URL + code) that
// arrive while a request is deferred.
OnRequirement func(Requirement)
// MaxBodyBytes bounds request-body buffering (default 4 MiB).
MaxBodyBytes int64
// RefreshMargin is how long before its exp a cached auth token is
// replaced rather than presented (default DefaultRefreshMargin, five
// minutes, §7.9.1). Person tokens follow PS.RefreshMargin.
RefreshMargin time.Duration
// MaxClockSkewWait bounds the wait before re-presenting a request a
// resource refused with clock_skew (default 10 seconds). A negative
// value disables the retry.
MaxClockSkewWait time.Duration
// contains filtered or unexported fields
}
Transport is a protocol-aware http.RoundTripper for agents. Wrap any HTTP client with it and AAuth disappears from application code:
hc := &http.Client{Transport: aauth.NewTransport(agent, ps)}
res, err := hc.Get("https://files.example/files") // just works
Per request it:
- signs with the agent's key, presenting the freshest credential for the target resource (its origin): a cached auth token, else — once the resource has asked for the person — a person token from the PS, else the agent token; and any cached session token (AAuth-Access, §6.3) bound into the signature;
- on 401 requirement=agent-token (§6.1), retries with the agent token;
- on 401 requirement=person-token (§6.4), obtains a person token for the resource from the PS (PSClient.PersonToken, §7.1) and retries;
- on requirement=auth-token (§6.5) — a 401, or a 202 deferred delivery holding the invocation (§6.5.1) — verifies the resource-token challenge against the token it presented (§6.7.3), redeems it at the PS auth token endpoint with that token as presented_token (§7.2), checks the auth token it receives (§9.4.4), caches it, and retries (401) or presents it on the polls of the pending URL (202). Step-up re-challenges trigger a fresh request;
- follows resource-managed 202 interaction and approval waits (§6.2, §11.6.4), surfacing the requirement via OnRequirement;
- honors session token rolling refresh: a new AAuth-Access value on any response replaces the cached session token (§6.3);
- refreshes top-down within the refresh margin (§7.9.1): a token with fewer than the margin left is not presented again — the agent token is minted fresh for each request, a person token is replaced through the PS cache, and an auth token is replaced by presenting the (fresh) person token and redeeming the new challenge;
- on 401 Signature-Error clock_skew, waits out the skew the response Date header shows (up to MaxClockSkewWait) and presents the same signed request once more (signature-key §5.4.14);
- on 401 expired_jwt or revoked_jwt for a cached person or auth token, drops it and falls back to the next credential (§11.12.5).
A sub-agent's Transport has the sub-agent as Agent and its parent's PSClient as PS (§10.2.3): the sub-agent signs its own requests to resources with its own key, while the parent signs the PS requests and the transport adds the sub-agent's token as subagent_token, so the person and auth tokens bind the sub-agent's key.
Request bodies are buffered in memory so retries can re-sign and resend; bound with MaxBodyBytes.
Example ¶
ExampleTransport wraps an http.Client so AAuth is transparent: signing, 401 challenges, token exchange, and caching all happen automatically.
package main
import (
"log"
"net/http"
aauth "github.com/aauth-dev/aauth-go"
)
func main() {
id, _ := aauth.ParseAgentIdentifier("aauth:assistant@agent.example")
agent, _ := aauth.NewAgent(id)
ps := aauth.NewPSClient("https://ps.example", agent)
hc := &http.Client{Transport: aauth.NewTransport(agent, ps)}
// This call signs itself, and if the resource answers 401 with an
// auth-token challenge, the transport exchanges a token and retries.
res, err := hc.Get("https://files.example/files")
if err != nil {
log.Printf("request failed: %v", err)
return
}
defer func() {
if err := res.Body.Close(); err != nil {
log.Printf("close body: %v", err)
}
}()
}
Output:
func NewTransport ¶
NewTransport builds a Transport for agent, obtaining tokens from ps (ps may be nil for identity-only use).
type UnsupportedSchemeError ¶
type UnsupportedSchemeError struct {
Scheme string // the scheme presented
Accepted []string // the schemes the verifier accepts here
}
UnsupportedSchemeError reports a Signature-Key scheme the verifier does not accept for this request, with the schemes it does accept (sent as Accept-Signature-Scheme). It matches ErrUnsupportedScheme.
func (*UnsupportedSchemeError) Error ¶
func (e *UnsupportedSchemeError) Error() string
Error implements error.
func (*UnsupportedSchemeError) Is ¶
func (e *UnsupportedSchemeError) Is(target error) bool
Is reports whether target is ErrUnsupportedScheme.
type UpstreamVerifyOptions ¶
type UpstreamVerifyOptions struct {
// TokenVerifyOptions locates the upstream token's issuer key.
TokenVerifyOptions
// Intermediary is the verified agent token of the intermediary making
// the downstream request — at the PS the one that signed it, at the AS
// the agent_token parameter. The intermediary is its own agent
// provider, so its iss is its resource identifier, which the upstream
// token's aud must equal (step 3, §10.1.1.1).
Intermediary *AgentClaims
// PS is, at a PS, its own identifier, and at an AS, the PS that signed
// the request. A person token's iss, or an auth token's ps, must equal
// it (step 2).
PS string
// AtAS selects the AS's issuer rule: an auth token's iss is not checked
// beyond its signature. At a PS (false), an auth token's iss must be
// the PS or an issuer TrustAuthIssuer accepts.
AtAS bool
// TrustAuthIssuer, at a PS, reports whether iss is an AS this PS
// presented a person token to for the token's aud and sub (§9.1.1).
// Nil accepts only the PS itself.
TrustAuthIssuer func(iss, aud, sub string) bool
}
UpstreamVerifyOptions tunes VerifyUpstreamToken.
type VerifyAgentTokenOptions ¶
type VerifyAgentTokenOptions struct {
// Resolver locates the token-signature key. Required.
Resolver KeyResolver
// RequireProviderClaims enforces the draft -11 agent-token profile
// strictly (§5.3.3, §11.5.2): iss must be a server identifier
// (§11.1.1), dwk must equal WellKnownAgent, jti must be present, and a
// ps claim, when present, must be a server identifier. Self-hosted
// local deployments MAY relax this (signature-key §3.8 makes iss/dwk
// SHOULD; the AAuth agent-token profile makes them MUST — set true for
// cross-domain interop).
RequireProviderClaims bool
// Signature tunes HTTP message-signature verification in
// VerifyAndExtractAgent (validity window, required components, clock).
Signature RequestVerifyOptions
}
VerifyAgentTokenOptions tunes VerifyAgentToken.
type VerifyServerOptions ¶
type VerifyServerOptions struct {
// Resolver discovers the caller's key from (id, dwk, kid); nil uses a
// JWKSResolver with DefaultJWKSCache and [DiscoveryClient], which
// reaches only public https destinations. Discovery verifies that the
// metadata issuer equals id (issuer_missing / issuer_mismatch).
Resolver KeyResolver
// DWKs, when non-empty, restricts the metadata documents (roles)
// accepted, e.g. []string{WellKnownPerson} at an AS token endpoint.
DWKs []string
// TrustSigner, when set, is consulted before any fetch; returning
// false rejects the caller with invalid_key. Verifiers SHOULD check id
// against expected origins (signature-key §7.3); a verifier that
// accepts unknown signers obtains an origin-bound pseudonym, not an
// authorized identity.
TrustSigner func(id, dwk string) bool
// Signature tunes HTTP message-signature verification. PS, AS, and
// revocation endpoints should set RequireBodyCoverage (§11.3.3.1).
Signature RequestVerifyOptions
// InsecureSkipIdentifierCheck accepts an id that is not a server
// identifier (§11.1.1: https, host only, lowercase), for development
// and tests against local servers. Never set it in production.
InsecureSkipIdentifierCheck bool
}
VerifyServerOptions tunes VerifyServerRequest.
Source Files
¶
- aauth.go
- agent.go
- alg.go
- audit.go
- auth.go
- chaining.go
- claims.go
- deferred.go
- discovery.go
- exchange.go
- httpsig.go
- identifier.go
- interaction.go
- interactionclient.go
- jwk.go
- jwks.go
- keys.go
- limiter.go
- metadata.go
- mission.go
- missionclient.go
- permission.go
- person.go
- personclient.go
- problem.go
- psclient.go
- requirement.go
- resource.go
- revocation.go
- serversig.go
- sigerror.go
- token.go
- tokenerror.go
- transport.go
Directories
¶
| Path | Synopsis |
|---|---|
|
Package accessserver implements the Access Server role of AAuth (draft-hardt-oauth-aauth-protocol-11 §9) and the PS side of PS-AS federation.
|
Package accessserver implements the Access Server role of AAuth (draft-hardt-oauth-aauth-protocol-11 §9) and the PS side of PS-AS federation. |
|
Package agentprovider implements a hosted agent provider for AAuth (draft-hardt-oauth-aauth-protocol-11 §5.1; draft-hardt-aauth-bootstrap-02).
|
Package agentprovider implements a hosted agent provider for AAuth (draft-hardt-oauth-aauth-protocol-11 §5.1; draft-hardt-aauth-bootstrap-02). |
|
Package e2e holds end-to-end tests that run the agent provider, person server, and access server packages together with an agent and resources, as a hosting application would deploy them.
|
Package e2e holds end-to-end tests that run the agent provider, person server, and access server packages together with an agent and resources, as a hosting application would deploy them. |
|
Package interactioncode generates and canonicalizes AAuth interaction codes (draft-hardt-oauth-aauth-protocol-11; format per the interaction-code requirements: unambiguous alphabet, minimum entropy).
|
Package interactioncode generates and canonicalizes AAuth interaction codes (draft-hardt-oauth-aauth-protocol-11; format per the interaction-code requirements: unambiguous alphabet, minimum entropy). |
|
Package personserver implements the Person Server role of AAuth (draft-hardt-oauth-aauth-protocol-11 §7): the person token endpoint, the auth token endpoint, deferred (pending) requests with clarification chat, and the metadata document and JWKS a PS publishes.
|
Package personserver implements the Person Server role of AAuth (draft-hardt-oauth-aauth-protocol-11 §7): the person token endpoint, the auth token endpoint, deferred (pending) requests with clarification chat, and the metadata document and JWKS a PS publishes. |
|
Package ratelimit provides an in-memory token-bucket implementation of aauth.Limiter for single-instance deployments.
|
Package ratelimit provides an in-memory token-bucket implementation of aauth.Limiter for single-instance deployments. |