agentauth

package
v1.1.0 Latest Latest
Warning

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

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

Documentation

Overview

Package agentauth is the auth.md agent-identity layer (api-v1-redesign §5 / Slice 5b). This file is the signing foundation (5b-1): an RS256 JWT signer loaded from a PEM private key, plus the public JWKS published at /.well-known/jwks.json that agents verify e2a-minted tokens against.

Key management mirrors the sibling agentdrive deployment: the private key is supplied as a PEM string via config/env (E2A_OAUTH_SIGNING_KEY), never generated or persisted by e2a, with a `kid` (E2A_OAUTH_SIGNING_KID, default "v1") advertised in the JWKS and stamped on every token. An empty key leaves the agent-auth surface DISABLED: the JWKS serves an empty key set and any attempt to sign returns ErrSigningDisabled, so dev/self-host deployments that don't use agent identity run unchanged. Rotation = advertise a new kid, then retire the old after the longest token TTL.

Index

Constants

View Source
const (
	TypIdentityAssertion = "identity_assertion"
	TypAccessToken       = "access_token"

	// IdentityAssertionTTL bounds the long-lived credential. Re-mintable any
	// time from the agent's e2a_agt_ key, so it need not be long; 30 days
	// matches the auth.md reference's anonymous assertion.
	IdentityAssertionTTL = 30 * 24 * time.Hour
	// AccessTokenTTL keeps the bearer short so a leak self-heals quickly. The
	// agent re-exchanges its identity_assertion when it expires.
	AccessTokenTTL = 15 * time.Minute
)

auth.md token model (Slice 5b-2). e2a issues two server-signed RS256 JWTs:

  • identity_assertion — the long-lived credential an agent holds (typ "identity_assertion"). Minted at POST /agent/identity once the agent has proven who it is (Slice 5b-2 bootstraps this with the agent's e2a_agt_ key). Re-presented at the token endpoint to mint access tokens.
  • access_token — the short-lived bearer (typ "access_token") the agent actually calls the API with. Minted at POST /oauth2/token via grant_type=jwt-bearer.

Both carry the agent's email as `sub`, the granted `scope` (always "agent" on this path), and the `assertion_version` of the agent row — the kill switch: a bump invalidates every assertion/token mintable from the old version.

View Source
const SigningAlg = jose.RS256

SigningAlg is the only algorithm e2a issues — RS256, what auth.md's reference implementation emits and what every JWKS consumer expects.

Variables

View Source
var ErrSigningDisabled = errors.New("agentauth: signing key not configured")

ErrSigningDisabled is returned by Sign when no signing key is configured. Handlers translate it into a 501/"agent auth not enabled" response rather than a 500, so an unconfigured deployment fails cleanly.

View Source
var ErrTokenInvalid = errors.New("agentauth: token invalid")

ErrTokenInvalid wraps every verification failure so callers can map the whole class to a 401/invalid_grant without leaking which check failed.

Functions

This section is empty.

Types

type Signer

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

Signer holds the parsed RSA private key and its key id. The zero value (and a Signer built from an empty PEM) is a valid, DISABLED signer.

func NewSigner

func NewSigner(pemKey, kid string) (*Signer, error)

NewSigner builds a Signer from a PEM-encoded RSA private key (PKCS#1 or PKCS#8) and a kid. An empty pemKey yields a disabled signer (no error) so the surface can be off by default; a non-empty but malformed key is a hard error so a misconfigured deployment fails fast at startup rather than at first sign.

func (*Signer) Enabled

func (s *Signer) Enabled() bool

Enabled reports whether a signing key is configured.

func (*Signer) KeyID

func (s *Signer) KeyID() string

KeyID returns the kid stamped on issued tokens (empty when disabled).

func (*Signer) PublicJWKS

func (s *Signer) PublicJWKS() jose.JSONWebKeySet

PublicJWKS returns the public half as a JWK set for /.well-known/jwks.json. When disabled it returns an empty (non-nil) set so the endpoint always serves valid JSON ({"keys":[]}).

func (*Signer) Sign

func (s *Signer) Sign(claims jwt.Claims, private map[string]interface{}) (string, error)

Sign issues an RS256 JWT carrying the given claims, with the kid in the header so verifiers can select the right JWKS entry. Returns ErrSigningDisabled when no key is configured.

func (*Signer) SignAccessToken

func (s *Signer) SignAccessToken(sub, scope string, assertionVersion int, issuer string) (string, time.Time, error)

SignAccessToken mints a short-lived access_token for sub. Same issuer/aud binding as the assertion. Returns the token and its absolute expiry.

func (*Signer) SignIdentityAssertion

func (s *Signer) SignIdentityAssertion(sub, scope string, assertionVersion int, issuer string) (string, time.Time, error)

SignIdentityAssertion mints a long-lived identity_assertion for sub (the agent email), bound to issuer (= the AS public URL, used as both iss and aud). Returns the token and its absolute expiry.

func (*Signer) VerifyToken

func (s *Signer) VerifyToken(token, expectedType, issuer string) (*VerifiedClaims, error)

VerifyToken validates an e2a-minted JWT: RS256 signature against the local public key, iss == issuer, aud contains issuer, not expired (with skew), and typ == expectedType. On success returns the typed claims; every failure wraps ErrTokenInvalid. assertion_version freshness is the caller's job (it needs the live agent row).

type VerifiedClaims

type VerifiedClaims struct {
	Subject          string
	Scope            string
	AssertionVersion int
	Type             string
	Expiry           time.Time
}

VerifiedClaims is the validated, typed result of VerifyToken.

Jump to

Keyboard shortcuts

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