Skip to content

Layered Identity Composition

AgentAuth's identity model consists of three complementary layers that are composed together, not chosen between.

Identity Layers

┌─────────────────────────────────────────────────────────────────┐
│                        Human Identity                           │
│  "Which user is this agent acting for?"                         │
│  Source: ID-JAG assertions from identity providers              │
└──────────────────────────┬──────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│                        Agent Identity                           │
│  "Which autonomous agent is this?"                              │
│  Source: AAuth tokens with HTTP message signatures              │
└──────────────────────────┬──────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│                      Workload Identity                          │
│  "Which workload/service is hosting this?"                      │
│  Source: SPIFFE/SPIRE with mTLS                                 │
└─────────────────────────────────────────────────────────────────┘

ComposedIdentity

The ComposedIdentity type links all three identity layers:

type ComposedIdentity struct {
    // Human identity (from ID-JAG delegation)
    Human *HumanIdentity `json:"human,omitempty"`

    // Agent identity (from AAuth)
    Agent *AgentIdentity `json:"agent"`

    // Workload identity (from SPIFFE)
    Workload *WorkloadIdentity `json:"workload,omitempty"`

    // BindingID uniquely identifies this composed identity binding
    BindingID string `json:"binding_id"`

    // BoundAt is when the identities were linked
    BoundAt time.Time `json:"bound_at"`

    // ExpiresAt is when the binding expires
    ExpiresAt time.Time `json:"expires_at,omitempty"`

    // TraceID for distributed tracing
    TraceID string `json:"trace_id,omitempty"`
}

Human Identity

Represents the human user the agent is acting for, obtained from ID-JAG assertions:

type HumanIdentity struct {
    Subject    string    `json:"sub"`           // User identifier
    Issuer     string    `json:"iss"`           // Identity provider
    Email      string    `json:"email,omitempty"`
    Name       string    `json:"name,omitempty"`
    Roles      []string  `json:"roles,omitempty"`
    IDJAGToken string    `json:"idjag_token,omitempty"`
    VerifiedAt time.Time `json:"verified_at,omitempty"`
}

Agent Identity

Represents the autonomous agent, obtained from AAuth tokens:

type AgentIdentity struct {
    AgentID      string    `json:"agent_id"`
    MissionID    string    `json:"mission_id,omitempty"`
    Issuer       string    `json:"iss"`
    Capabilities []string  `json:"capabilities,omitempty"`
    Scopes       []string  `json:"scopes,omitempty"`
    DelegatedBy  string    `json:"delegated_by,omitempty"`
    AAuthToken   string    `json:"aauth_token,omitempty"`
    VerifiedAt   time.Time `json:"verified_at,omitempty"`
}

Workload Identity

Represents the infrastructure workload hosting the agent, from SPIFFE/SPIRE:

type WorkloadIdentity struct {
    SPIFFEID    string    `json:"spiffe_id"`
    TrustDomain string    `json:"trust_domain,omitempty"`
    ServiceName string    `json:"service_name,omitempty"`
    SVID        string    `json:"svid,omitempty"`
    VerifiedAt  time.Time `json:"verified_at,omitempty"`
}

Identity Composer

The Composer creates and validates composed identities:

import "github.com/plexusone/agentauth/identity"

// Create composer with verifiers
composer := identity.NewComposer(
    identity.WithAAuthVerifier(aauthVerifier),
    identity.WithIDJAGVerifier(idjagVerifier),
    identity.WithWorkloadVerifier(spiffeVerifier),
)

// Compose from credentials
composed, err := composer.Compose(ctx, identity.ComposeOptions{
    AAuthToken:      aauthToken,
    IDJAGAssertion:  idjagAssertion,
    IncludeWorkload: true,
})
if err != nil {
    return err
}

// Use for authorization
fmt.Println(composed.AuditString())
// agent:research-agent for-human:alice@example.com on-workload:spiffe://prod/research binding:bind-xyz

Context Integration

Store and retrieve composed identity from context:

// Store in context
ctx = identity.WithContext(ctx, composed)

// Retrieve from context
if id, ok := identity.FromContext(ctx); ok {
    // Use identity for authorization decisions
    if id.HasHuman() && id.Human.Subject == expectedUser {
        // Authorized
    }
}

Validation

Check identity validity and expiration:

// Check if identity has required components
if !composed.IsValid() {
    return errors.New("invalid identity: missing agent")
}

// Check if binding has expired
if composed.IsExpired() {
    return errors.New("identity binding expired")
}

// Check for human identity
if !composed.HasHuman() {
    return errors.New("human identity required for this operation")
}