auth

package
v1.41.1 Latest Latest
Warning

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

Go to latest
Published: Apr 19, 2026 License: MIT Imports: 28 Imported by: 0

Documentation

Overview

Package auth provides authentication and authorization for Hector v2.

This package is ported from legacy Hector (pkg/auth) and adapted to work with a2a-go's CallInterceptor system for seamless integration.

Architecture

The auth package follows a layered approach:

  1. JWTValidator: Validates JWT tokens using JWKS (JSON Web Key Set)
  2. HTTP Middleware: Extracts and validates tokens from HTTP requests
  3. CallInterceptor: Bridges to a2a-go's authentication system

Usage

Configure authentication in your hector.yaml:

server:
  auth:
    enabled: true
    jwks_url: "https://auth.example.com/.well-known/jwks.json"
    issuer: "https://auth.example.com"
    audience: "hector-api"

The auth middleware will automatically validate JWT tokens and make claims available to agents via the invocation context.

Index

Constants

View Source
const (
	// ClaimsContextKey is the context key for storing validated claims.
	ClaimsContextKey contextKey = "hector_auth_claims"
)

Variables

View Source
var (
	// ErrUnauthorized is returned when authentication is required but not provided.
	ErrUnauthorized = errors.New("unauthorized: authentication required")

	// ErrForbidden is returned when the user lacks permission.
	ErrForbidden = errors.New("forbidden: insufficient permissions")

	// ErrInvalidToken is returned when a token cannot be validated.
	ErrInvalidToken = errors.New("invalid token")

	// ErrTokenExpired is returned when a token has expired.
	ErrTokenExpired = errors.New("token expired")

	// ErrMissingClaims is returned when required claims are missing.
	ErrMissingClaims = errors.New("missing required claims")
)

Common authentication errors.

Functions

func ContextWithClaims

func ContextWithClaims(ctx context.Context, claims *Claims) context.Context

ContextWithClaims returns a new context with the given claims.

func GenerateDeterministicEd25519KeyPEM added in v1.21.0

func GenerateDeterministicEd25519KeyPEM(seed string) ([]byte, error)

GenerateDeterministicEd25519KeyPEM generates an Ed25519 private key deterministically from a seed.

func GenerateDeterministicRSAPrivateKeyPEM added in v1.21.0

func GenerateDeterministicRSAPrivateKeyPEM(seed string) ([]byte, error)

GenerateDeterministicRSAPrivateKeyPEM generates an RSA-2048 private key deterministically from a seed. NOTE: RSA generation in Go is not perfectly deterministic even with a fixed reader. This function is kept for backward compatibility but use of Ed25519 is recommended for true determinism.

func LoadOrGenerateRSAPrivateKey added in v1.21.0

func LoadOrGenerateRSAPrivateKey(path string) ([]byte, error)

LoadOrGenerateRSAPrivateKey loads a PEM-encoded private key from disk or generates a new one. It uses standard crypto/rsa (which jwx builds upon) to ensure compatible PEM format.

func Middleware

func Middleware(validator TokenValidator) func(http.Handler) http.Handler

Middleware creates an HTTP middleware that validates JWT tokens. Requests without valid tokens receive 401 Unauthorized.

The middleware extracts the token from the Authorization header:

  • "Bearer <token>" format (preferred)
  • Raw token (fallback)

Valid claims are stored in the request context and can be retrieved using ClaimsFromContext().

func MiddlewareWithExclusions

func MiddlewareWithExclusions(validator TokenValidator, excludedPaths []string) func(http.Handler) http.Handler

MiddlewareWithExclusions creates a middleware that skips auth for certain paths. This is useful for health checks, public endpoints, etc.

func OptionalMiddleware

func OptionalMiddleware(validator TokenValidator) func(http.Handler) http.Handler

OptionalMiddleware validates tokens if present but doesn't require them. If a valid token is present, claims are added to the context. If no token is present, the request proceeds without claims. If an invalid token is present, the request is rejected.

func RequireRole

func RequireRole(roles ...string) func(http.Handler) http.Handler

RequireRole creates a middleware that requires the user to have a specific role. Must be used after Middleware() in the chain.

func RequireTenant

func RequireTenant(tenants ...string) func(http.Handler) http.Handler

RequireTenant creates a middleware that requires the user to belong to a specific tenant. Must be used after Middleware() in the chain.

Types

type AuthenticatedUser

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

AuthenticatedUser implements a2asrv.User interface. It wraps Hector's Claims to provide user information to a2a-go.

func UserFromCallContext

func UserFromCallContext(callCtx *a2asrv.CallContext) *AuthenticatedUser

UserFromCallContext extracts the AuthenticatedUser from a2a-go CallContext. Returns nil if the user is not authenticated or not an AuthenticatedUser.

func (*AuthenticatedUser) Authenticated

func (u *AuthenticatedUser) Authenticated() bool

Authenticated returns true since this represents an authenticated user.

func (*AuthenticatedUser) Claims

func (u *AuthenticatedUser) Claims() *Claims

Claims returns the underlying Hector claims. This allows access to full claim data when needed.

func (*AuthenticatedUser) Email

func (u *AuthenticatedUser) Email() string

Email returns the user's email address.

func (*AuthenticatedUser) Name

func (u *AuthenticatedUser) Name() string

Name returns the user's subject (unique identifier).

func (*AuthenticatedUser) Role

func (u *AuthenticatedUser) Role() string

Role returns the user's role.

func (*AuthenticatedUser) TenantID

func (u *AuthenticatedUser) TenantID() string

TenantID returns the user's tenant ID.

type Claims

type Claims struct {
	// Subject is the unique identifier for the user (sub claim).
	Subject string `json:"sub"`

	// Email is the user's email address (if provided).
	Email string `json:"email,omitempty"`

	// Role is the user's role for authorization decisions.
	Role string `json:"role,omitempty"`

	// TenantID supports multi-tenant applications.
	TenantID string `json:"tenant_id,omitempty"`

	// Custom contains any additional claims not mapped to struct fields.
	Custom map[string]any `json:"-"`
}

Claims represents the validated claims from a JWT token. This structure is designed to support common identity providers (Auth0, Okta, Keycloak, etc.) while being extensible for custom claims.

func ClaimsFromCallContext

func ClaimsFromCallContext(callCtx *a2asrv.CallContext) *Claims

ClaimsFromCallContext extracts Claims from a2a-go CallContext. Returns nil if the user is not authenticated.

func ClaimsFromContext

func ClaimsFromContext(ctx context.Context) *Claims

ClaimsFromContext extracts claims from a context. Returns nil if no claims are present.

func (*Claims) GetClaim

func (c *Claims) GetClaim(key string) (any, bool)

GetClaim retrieves a custom claim by key.

func (*Claims) GetStringClaim

func (c *Claims) GetStringClaim(key string) string

GetStringClaim retrieves a custom claim as a string.

func (*Claims) HasAnyRole

func (c *Claims) HasAnyRole(roles ...string) bool

HasAnyRole checks if the user has any of the specified roles.

func (*Claims) HasRole

func (c *Claims) HasRole(role string) bool

HasRole checks if the user has a specific role.

type CompositeValidator added in v1.15.1

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

CompositeValidator validates tokens against multiple validators. It returns the claims from the first validator that succeeds.

func NewCompositeValidator added in v1.15.1

func NewCompositeValidator(validators ...TokenValidator) *CompositeValidator

NewCompositeValidator creates a new CompositeValidator.

func (*CompositeValidator) Close added in v1.15.1

func (v *CompositeValidator) Close() error

Close closes all validators.

func (*CompositeValidator) ValidateToken added in v1.15.1

func (v *CompositeValidator) ValidateToken(ctx context.Context, tokenString string) (*Claims, error)

ValidateToken tries each validator in order.

type CredentialType

type CredentialType string

CredentialType identifies the type of credential for outbound requests.

const (
	CredentialTypeBearer CredentialType = "bearer"
	CredentialTypeAPIKey CredentialType = "api_key"
	CredentialTypeBasic  CredentialType = "basic"
)

type DeterministicReader added in v1.21.0

type DeterministicReader struct {
	H       hash.Hash
	Seed    []byte
	Counter uint32
	Buf     []byte
}

DeterministicReader is a reader that produces a deterministic stream of bytes from a seed.

func (*DeterministicReader) Read added in v1.21.0

func (r *DeterministicReader) Read(p []byte) (n int, err error)

type Interceptor

type Interceptor struct {
	// RequireAuth when true rejects unauthenticated requests.
	// When false, unauthenticated requests proceed with a nil User.
	RequireAuth bool
}

Interceptor bridges Hector's auth system to a2a-go's CallInterceptor. It reads Claims from the HTTP context (set by Middleware) and sets the a2a-go CallContext.User field.

Architecture

The auth flow is:

  1. HTTP request arrives
  2. auth.Middleware validates JWT and stores Claims in context
  3. a2a-go JSON-RPC handler processes the request
  4. auth.Interceptor.Before() reads Claims and sets CallContext.User
  5. Executor/Agent can access User via CallContext

This design keeps JWT validation in standard HTTP middleware while bridging to a2a-go's authentication system.

func NewInterceptor

func NewInterceptor(requireAuth bool) *Interceptor

NewInterceptor creates a new auth interceptor.

func (*Interceptor) After

func (i *Interceptor) After(ctx context.Context, callCtx *a2asrv.CallContext, resp *a2asrv.Response) error

After is called after each a2a-go request handler method. Currently a no-op but can be extended for audit logging.

func (*Interceptor) Before

func (i *Interceptor) Before(ctx context.Context, callCtx *a2asrv.CallContext, req *a2asrv.Request) (context.Context, error)

Before is called before each a2a-go request handler method. It bridges Claims from HTTP context to a2a-go's User interface.

type JWTValidator

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

JWTValidator validates JWT tokens using JWKS (JSON Web Key Set). It supports automatic key rotation via the jwk.Cache.

This is ported from legacy Hector (pkg/auth/jwt.go) with the same production-tested implementation.

func NewJWTValidator

func NewJWTValidator(cfg JWTValidatorConfig) (*JWTValidator, error)

NewJWTValidator creates a new JWT validator. It fetches the JWKS from the provided URL and caches the keys.

func (*JWTValidator) Close

func (v *JWTValidator) Close() error

Close releases resources held by the validator. Currently a no-op but provided for future resource cleanup.

func (*JWTValidator) ValidateToken

func (v *JWTValidator) ValidateToken(ctx context.Context, tokenString string) (*Claims, error)

ValidateToken validates a JWT token string and returns the extracted claims. It verifies:

  • Token signature against JWKS
  • Token expiration (exp claim)
  • Token not-before time (nbf claim)
  • Issuer (iss claim)
  • Audience (aud claim)

type JWTValidatorConfig

type JWTValidatorConfig struct {
	// JWKSURL is the URL to fetch JSON Web Key Set from.
	// Example: "https://auth.example.com/.well-known/jwks.json"
	JWKSURL string

	// Issuer is the expected token issuer (iss claim).
	// Example: "https://auth.example.com"
	Issuer string

	// Audience is the expected token audience (aud claim).
	// Example: "hector-api"
	Audience string

	// RefreshInterval is how often to refresh the JWKS.
	// Default: 15 minutes
	RefreshInterval time.Duration
}

JWTValidatorConfig configures the JWT validator.

type MultiIssuerConfig added in v1.21.0

type MultiIssuerConfig struct {
	// SelfIssuer is the issuer URL for hector-issued tokens.
	SelfIssuer string

	// SelfValidator validates hector-issued tokens.
	SelfValidator TokenValidator

	// ExternalValidators maps external issuer URLs to their JWKS validators.
	ExternalValidators map[string]TokenValidator
}

MultiIssuerConfig configures the multi-issuer validator.

type MultiIssuerValidator added in v1.21.0

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

MultiIssuerValidator validates JWTs from multiple issuers. It extracts the issuer claim from the token and routes to the correct validator. This enables unified auth for both hector-issued tokens and external OIDC tokens.

func NewMultiIssuerValidator added in v1.21.0

func NewMultiIssuerValidator(cfg MultiIssuerConfig) (*MultiIssuerValidator, error)

NewMultiIssuerValidator creates a new multi-issuer validator.

func (*MultiIssuerValidator) AddExternalValidator added in v1.21.0

func (v *MultiIssuerValidator) AddExternalValidator(issuer string, validator TokenValidator)

AddExternalValidator adds an external validator for a given issuer.

func (*MultiIssuerValidator) Close added in v1.21.0

func (v *MultiIssuerValidator) Close() error

Close closes all validators.

func (*MultiIssuerValidator) ValidateToken added in v1.21.0

func (v *MultiIssuerValidator) ValidateToken(ctx context.Context, tokenString string) (*Claims, error)

ValidateToken validates a JWT token by routing to the correct validator based on issuer.

type SecretValidator added in v1.15.1

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

SecretValidator validates tokens against a shared secret.

func NewSecretValidator added in v1.15.1

func NewSecretValidator(secret string) *SecretValidator

NewSecretValidator creates a new SecretValidator.

func (*SecretValidator) Close added in v1.15.1

func (v *SecretValidator) Close() error

Close is a no-op for SecretValidator.

func (*SecretValidator) ValidateToken added in v1.15.1

func (v *SecretValidator) ValidateToken(ctx context.Context, tokenString string) (*Claims, error)

ValidateToken validates the token against the shared secret. It uses constant-time comparison to prevent timing attacks.

type TokenIssuer added in v1.21.0

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

TokenIssuer issues JWT tokens for apps. It uses asymmetric keys (RSA or Ed25519) for signing and provides a JWKS endpoint for verification.

func NewTokenIssuer added in v1.21.0

func NewTokenIssuer(cfg TokenIssuerConfig) (*TokenIssuer, error)

NewTokenIssuer creates a new token issuer. If no private key is provided, it generates a new RSA-2048 key pair.

func (*TokenIssuer) Close added in v1.21.0

func (i *TokenIssuer) Close() error

Close releases resources held by the issuer.

func (*TokenIssuer) IssueAdminToken added in v1.21.0

func (i *TokenIssuer) IssueAdminToken(subject string, ttl time.Duration) (string, error)

IssueAdminToken creates a JWT token with admin role.

func (*TokenIssuer) IssueToken added in v1.21.0

func (i *TokenIssuer) IssueToken(appID string, ttl time.Duration) (string, error)

IssueToken creates a new JWT token for the given app.

func (*TokenIssuer) Issuer added in v1.21.0

func (i *TokenIssuer) Issuer() string

Issuer returns the issuer URL.

func (*TokenIssuer) PublicJWKS added in v1.21.0

func (i *TokenIssuer) PublicJWKS() jwk.Set

PublicJWKS returns the public key set for external verification. This should be served at /.well-known/jwks.json

func (*TokenIssuer) ValidateToken added in v1.21.0

func (i *TokenIssuer) ValidateToken(ctx context.Context, tokenString string) (*Claims, error)

ValidateToken validates a token issued by this issuer. This is used for in-process validation without HTTP round-trip.

type TokenIssuerConfig added in v1.21.0

type TokenIssuerConfig struct {
	// Issuer is the issuer claim (iss) for tokens.
	// Example: "https://hector.example.com"
	Issuer string

	// Audience is the default audience claim (aud) for tokens.
	// Example: "hector-api"
	Audience string

	// PrivateKeyPEM is optional pre-existing signing key (RSA or Ed25519) in PEM format.
	// If not provided, a new RSA key pair will be generated.
	PrivateKeyPEM []byte
}

TokenIssuerConfig configures the token issuer.

type TokenProvider

type TokenProvider func() (string, error)

TokenProvider is a function that returns a token for outbound requests.

func NewTokenProvider

func NewTokenProvider(credType CredentialType, token, apiKey, username, password string) (TokenProvider, error)

NewTokenProvider creates a TokenProvider based on credential configuration. This is useful for making authenticated outbound requests (e.g., to remote agents).

type TokenValidator

type TokenValidator interface {
	ValidateToken(ctx context.Context, tokenString string) (*Claims, error)
	Close() error
}

TokenValidator is the interface for token validation. This allows for alternative implementations (e.g., for testing).

func NewValidatorFromConfig

func NewValidatorFromConfig(cfg *config.AuthConfig) (TokenValidator, error)

NewValidatorFromConfig creates a TokenValidator from configuration. Returns nil if authentication is not enabled.

Jump to

Keyboard shortcuts

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