idjag

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: May 11, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Overview

Package idjag implements Identity Assertion JWT Authorization Grant (ID-JAG) based on draft-ietf-oauth-identity-assertion-authz-grant.

ID-JAG enables secure token exchange where an agent presents a signed JWT assertion to obtain an access token. The assertion can represent:

  • An agent acting on its own behalf (no delegation)
  • An agent acting on behalf of a human (delegation via "act" claim)
  • Nested delegation chains (multiple levels of actors)

EXPERIMENTAL

This package implements a draft specification that is subject to change. The API may change in backwards-incompatible ways as the specification evolves.

Protocol Overview

The ID-JAG flow involves three parties:

  • Assertion Issuer: Creates and signs the identity assertion JWT
  • Authorization Server: Validates assertions and issues access tokens
  • Resource Server: Accepts access tokens to authorize requests

Basic flow:

  1. Agent obtains or creates a signed identity assertion
  2. Agent sends assertion to Authorization Server via token exchange
  3. Authorization Server validates assertion and returns access token
  4. Agent uses access token to call Resource Server

Delegation

When an agent acts on behalf of a human, the assertion includes an "act" (actor) claim identifying the agent:

{
  "iss": "https://issuer.example.com",
  "sub": "user:alice",
  "act": {
    "sub": "agent:calendar-bot"
  }
}

Nested delegation is supported by including nested "act" claims.

References

Index

Constants

View Source
const (
	ClaimIssuer         = "iss"
	ClaimSubject        = "sub"
	ClaimAudience       = "aud"
	ClaimExpirationTime = "exp"
	ClaimNotBefore      = "nbf"
	ClaimIssuedAt       = "iat"
	ClaimJWTID          = "jti"
)

Standard JWT claim names per RFC 7519.

View Source
const (
	// ClaimActor is the "act" claim for delegation per RFC 8693.
	// It contains information about the acting party when delegation is used.
	ClaimActor = "act"

	// ClaimClientID identifies the client making the token exchange request.
	ClaimClientID = "client_id"

	// ClaimScope contains the requested scope for the access token.
	ClaimScope = "scope"
)

ID-JAG specific claim names.

View Source
const (
	// GrantTypeTokenExchange is the grant type for RFC 8693 token exchange.
	GrantTypeTokenExchange = "urn:ietf:params:oauth:grant-type:token-exchange"

	// GrantTypeJWTBearer is the grant type for RFC 7523 JWT bearer assertion.
	GrantTypeJWTBearer = "urn:ietf:params:oauth:grant-type:jwt-bearer"
)

Grant types for token exchange.

View Source
const (
	// TokenTypeAccessToken indicates an OAuth 2.0 access token.
	TokenTypeAccessToken = "urn:ietf:params:oauth:token-type:access_token"

	// TokenTypeRefreshToken indicates an OAuth 2.0 refresh token.
	TokenTypeRefreshToken = "urn:ietf:params:oauth:token-type:refresh_token"

	// TokenTypeIDToken indicates an OpenID Connect ID token.
	TokenTypeIDToken = "urn:ietf:params:oauth:token-type:id_token"

	// TokenTypeJWT indicates a JWT token.
	TokenTypeJWT = "urn:ietf:params:oauth:token-type:jwt"

	// TokenTypeSAML1 indicates a SAML 1.1 assertion.
	TokenTypeSAML1 = "urn:ietf:params:oauth:token-type:saml1"

	// TokenTypeSAML2 indicates a SAML 2.0 assertion.
	TokenTypeSAML2 = "urn:ietf:params:oauth:token-type:saml2"
)

Token types for token exchange requests.

View Source
const (
	AlgorithmRS256 = "RS256"
	AlgorithmRS384 = "RS384"
	AlgorithmRS512 = "RS512"
	AlgorithmES256 = "ES256"
	AlgorithmES384 = "ES384"
	AlgorithmES512 = "ES512"
	AlgorithmPS256 = "PS256"
	AlgorithmPS384 = "PS384"
	AlgorithmPS512 = "PS512"
)

Supported JWT signing algorithms.

View Source
const (
	// JWKSPath is the standard path for JWKS endpoint.
	JWKSPath = "/.well-known/jwks.json"

	// OpenIDConfigPath is the standard path for OpenID Connect discovery.
	OpenIDConfigPath = "/.well-known/openid-configuration"
)

JWKS (JSON Web Key Set) constants.

View Source
const (
	HeaderAuthorization = "Authorization"
	HeaderContentType   = "Content-Type"
)

HTTP header names.

View Source
const (
	ContentTypeFormURLEncoded = "application/x-www-form-urlencoded"
	ContentTypeJSON           = "application/json"
)

Content type values.

View Source
const (
	ErrorInvalidRequest       = "invalid_request"
	ErrorInvalidClient        = "invalid_client"
	ErrorInvalidGrant         = "invalid_grant"
	ErrorUnauthorizedClient   = "unauthorized_client"
	ErrorUnsupportedGrantType = "unsupported_grant_type"
	ErrorInvalidScope         = "invalid_scope"
)

Common OAuth 2.0 error codes.

Variables

View Source
var (
	// ErrInvalidAssertion indicates the assertion JWT is malformed or invalid.
	ErrInvalidAssertion = errors.New("idjag: invalid assertion")

	// ErrExpiredAssertion indicates the assertion has expired (exp claim).
	ErrExpiredAssertion = errors.New("idjag: assertion expired")

	// ErrInvalidIssuer indicates the issuer claim doesn't match expected value.
	ErrInvalidIssuer = errors.New("idjag: invalid issuer")

	// ErrInvalidAudience indicates the audience claim doesn't match expected value.
	ErrInvalidAudience = errors.New("idjag: invalid audience")

	// ErrInvalidSubject indicates the subject claim is missing or invalid.
	ErrInvalidSubject = errors.New("idjag: invalid subject")

	// ErrSignatureInvalid indicates the JWT signature verification failed.
	ErrSignatureInvalid = errors.New("idjag: signature verification failed")

	// ErrKeyNotFound indicates the signing key was not found in JWKS.
	ErrKeyNotFound = errors.New("idjag: signing key not found")

	// ErrTokenExchangeFailed indicates the token exchange request failed.
	ErrTokenExchangeFailed = errors.New("idjag: token exchange failed")

	// ErrUnsupportedAlgorithm indicates the JWT uses an unsupported signing algorithm.
	ErrUnsupportedAlgorithm = errors.New("idjag: unsupported signing algorithm")

	// ErrMissingRequiredClaim indicates a required JWT claim is missing.
	ErrMissingRequiredClaim = errors.New("idjag: missing required claim")
)

Package-level errors for ID-JAG operations.

Functions

func ContextWithAssertion

func ContextWithAssertion(ctx context.Context, assertion *Assertion) context.Context

ContextWithAssertion adds an Assertion to the context.

Types

type Actor

type Actor struct {
	// Subject identifies the actor (sub claim within act).
	Subject string `json:"sub"`

	// Issuer optionally identifies who asserted this actor's identity.
	Issuer string `json:"iss,omitempty"`

	// Actor allows for nested delegation chains.
	// For example, User -> Agent1 -> Agent2.
	Actor *Actor `json:"act,omitempty"`
}

Actor represents the acting party in a delegation chain. Per RFC 8693, this appears in the "act" claim.

type Assertion

type Assertion struct {
	// Issuer identifies the principal that issued the assertion (iss claim).
	Issuer string `json:"iss"`

	// Subject identifies the principal that is the subject of the assertion (sub claim).
	// For direct agent authentication, this is the agent's identity.
	// For delegation, this is the human/principal identity being delegated.
	Subject string `json:"sub"`

	// Audience identifies the recipients that the assertion is intended for (aud claim).
	// Typically includes the authorization server's token endpoint.
	Audience []string `json:"aud"`

	// IssuedAt is the time at which the assertion was issued (iat claim).
	IssuedAt time.Time `json:"iat"`

	// ExpiresAt is the expiration time of the assertion (exp claim).
	ExpiresAt time.Time `json:"exp"`

	// NotBefore is the time before which the assertion is not valid (nbf claim).
	NotBefore time.Time `json:"nbf,omitempty"`

	// JWTID is a unique identifier for the assertion (jti claim).
	JWTID string `json:"jti,omitempty"`

	// Actor identifies the acting party in delegation scenarios (act claim).
	// When present, Subject identifies the delegating principal and Actor
	// identifies the party acting on their behalf.
	Actor *Actor `json:"act,omitempty"`

	// Claims contains additional custom claims not covered by the standard fields.
	Claims map[string]any `json:"-"`
}

Assertion represents an ID-JAG identity assertion. It contains the standard JWT claims plus the optional "act" claim for delegation.

func AssertionFromContext

func AssertionFromContext(ctx context.Context) *Assertion

AssertionFromContext retrieves an Assertion from the context. Returns nil if no assertion is present.

func NewAssertion

func NewAssertion(issuer, subject string, audience []string, ttl time.Duration) *Assertion

NewAssertion creates a new Assertion with common fields populated.

func NewDelegatedAssertion

func NewDelegatedAssertion(issuer, subject, actorSubject string, audience []string, ttl time.Duration) *Assertion

NewDelegatedAssertion creates an assertion representing delegation. The subject is the delegating principal (e.g., human user), and the actorSubject is the acting party (e.g., agent).

func ParseAssertion

func ParseAssertion(tokenString string) (*Assertion, error)

ParseAssertion parses a JWT string into an Assertion without verification. Use Verifier.Verify for validated parsing.

func (*Assertion) DelegationChain

func (a *Assertion) DelegationChain() []*Actor

DelegationChain returns the full chain of actors from outermost to innermost. For non-delegated assertions, returns nil.

func (*Assertion) IsDelegated

func (a *Assertion) IsDelegated() bool

IsDelegated returns true if the assertion represents delegation (has an actor).

func (*Assertion) IsExpired

func (a *Assertion) IsExpired() bool

IsExpired returns true if the assertion has expired.

func (*Assertion) MarshalJSON

func (a *Assertion) MarshalJSON() ([]byte, error)

MarshalJSON implements custom JSON marshaling for Assertion.

func (*Assertion) Sign

func (a *Assertion) Sign(method jwt.SigningMethod, key crypto.PrivateKey, keyID string) (string, error)

Sign creates a signed JWT string from the assertion.

func (*Assertion) WithActor

func (a *Assertion) WithActor(actor *Actor) *Assertion

WithActor adds an actor to the assertion for delegation.

func (*Assertion) WithClaim

func (a *Assertion) WithClaim(name string, value any) *Assertion

WithClaim adds a custom claim to the assertion.

type AuthorizationServer

type AuthorizationServer struct {
	// Verifier validates incoming assertions.
	Verifier Verifier

	// SigningMethod is the JWT signing method for access tokens.
	SigningMethod jwt.SigningMethod

	// SigningKey is the private key for signing access tokens.
	SigningKey crypto.PrivateKey

	// KeyID is the key identifier to include in token headers.
	KeyID string

	// Issuer is the issuer claim for access tokens.
	Issuer string

	// TokenTTL is the lifetime for issued access tokens.
	TokenTTL time.Duration

	// AllowedScopes restricts which scopes can be requested.
	// If empty, all scopes are allowed.
	AllowedScopes []string

	// ScopeValidator is an optional function to validate scope requests.
	// If set, it is called to validate the requested scope against the assertion.
	ScopeValidator func(assertion *Assertion, requestedScope string) error
}

AuthorizationServer handles token exchange requests for ID-JAG assertions.

func NewAuthorizationServer

func NewAuthorizationServer(verifier Verifier, signingMethod jwt.SigningMethod, signingKey crypto.PrivateKey, keyID, issuer string) *AuthorizationServer

NewAuthorizationServer creates a new authorization server.

func (*AuthorizationServer) ServeHTTP

func (s *AuthorizationServer) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP implements http.Handler for the token endpoint.

type JWK

type JWK struct {
	KeyType   string `json:"kty"`
	KeyID     string `json:"kid"`
	Algorithm string `json:"alg,omitempty"`
	Use       string `json:"use,omitempty"`

	// RSA parameters
	N string `json:"n,omitempty"` // Modulus
	E string `json:"e,omitempty"` // Exponent

	// EC parameters
	Curve string `json:"crv,omitempty"` // Curve name
	X     string `json:"x,omitempty"`   // X coordinate
	Y     string `json:"y,omitempty"`   // Y coordinate
}

JWK represents a JSON Web Key.

func NewJWKFromECPublicKey

func NewJWKFromECPublicKey(pubKey *ecdsa.PublicKey, keyID, algorithm string) JWK

NewJWKFromECPublicKey creates a JWK from an EC public key.

func NewJWKFromRSAPublicKey

func NewJWKFromRSAPublicKey(pubKey *rsa.PublicKey, keyID, algorithm string) JWK

NewJWKFromRSAPublicKey creates a JWK from an RSA public key.

func (*JWK) ToPublicKey

func (k *JWK) ToPublicKey() (crypto.PublicKey, error)

ToPublicKey converts a JWK to a crypto.PublicKey.

type JWKS

type JWKS struct {
	Keys []JWK `json:"keys"`
}

JWKS represents a JSON Web Key Set.

type JWKSHandler

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

JWKSHandler serves a JWKS endpoint.

func NewJWKSHandler

func NewJWKSHandler(jwks *JWKS) *JWKSHandler

NewJWKSHandler creates a handler that serves the given JWKS.

func (*JWKSHandler) ServeHTTP

func (h *JWKSHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP implements http.Handler.

type JWKSVerifier

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

JWKSVerifier verifies JWTs using keys fetched from a JWKS endpoint.

func NewJWKSVerifier

func NewJWKSVerifier(jwksURL string, opts VerifierOptions) *JWKSVerifier

NewJWKSVerifier creates a verifier that fetches keys from a JWKS endpoint.

func (*JWKSVerifier) Verify

func (v *JWKSVerifier) Verify(ctx context.Context, tokenString string) (*Assertion, error)

Verify validates the JWT signature and claims using JWKS.

func (*JWKSVerifier) WithCacheTTL

func (v *JWKSVerifier) WithCacheTTL(ttl time.Duration) *JWKSVerifier

WithCacheTTL sets the cache duration for JWKS keys.

func (*JWKSVerifier) WithHTTPClient

func (v *JWKSVerifier) WithHTTPClient(client *http.Client) *JWKSVerifier

WithHTTPClient sets a custom HTTP client for JWKS fetching.

type JWTBearerClient

type JWTBearerClient struct {
	// TokenURL is the authorization server's token endpoint.
	TokenURL string

	// HTTPClient is the HTTP client to use for requests.
	HTTPClient *http.Client

	// ClientID is the OAuth client identifier.
	ClientID string
}

JWTBearerClient handles JWT bearer assertion grants per RFC 7523.

func NewJWTBearerClient

func NewJWTBearerClient(tokenURL string) *JWTBearerClient

NewJWTBearerClient creates a new JWT bearer client.

func (*JWTBearerClient) Exchange

func (c *JWTBearerClient) Exchange(ctx context.Context, assertion, scope string) (*TokenExchangeResponse, error)

Exchange exchanges a JWT assertion for an access token using JWT bearer grant.

type ResourceServer

type ResourceServer struct {
	// Verifier validates incoming access tokens.
	Verifier Verifier
}

ResourceServer provides middleware for validating access tokens.

func NewResourceServer

func NewResourceServer(verifier Verifier) *ResourceServer

NewResourceServer creates a new resource server middleware.

func (*ResourceServer) Middleware

func (rs *ResourceServer) Middleware(next http.Handler) http.Handler

Middleware returns an HTTP middleware that validates Bearer tokens.

type StaticKeyVerifier

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

StaticKeyVerifier verifies JWTs using a pre-configured public key.

func NewStaticKeyVerifier

func NewStaticKeyVerifier(publicKey crypto.PublicKey, keyID string, opts VerifierOptions) *StaticKeyVerifier

NewStaticKeyVerifier creates a verifier with a single public key.

func (*StaticKeyVerifier) Verify

func (v *StaticKeyVerifier) Verify(ctx context.Context, tokenString string) (*Assertion, error)

Verify validates the JWT signature and claims.

type TokenErrorResponse

type TokenErrorResponse struct {
	Error            string `json:"error"`
	ErrorDescription string `json:"error_description,omitempty"`
	ErrorURI         string `json:"error_uri,omitempty"`
}

TokenErrorResponse represents an OAuth 2.0 error response from the authorization server during token exchange.

type TokenExchangeClient

type TokenExchangeClient struct {
	// TokenURL is the authorization server's token endpoint.
	TokenURL string

	// HTTPClient is the HTTP client to use for requests.
	// If nil, http.DefaultClient is used.
	HTTPClient *http.Client

	// ClientID is the OAuth client identifier.
	// If set, it will be included in the request.
	ClientID string

	// ClientSecret is the OAuth client secret.
	// If set, HTTP Basic authentication will be used.
	ClientSecret string
}

TokenExchangeClient handles OAuth 2.0 token exchange requests per RFC 8693.

func NewTokenExchangeClient

func NewTokenExchangeClient(tokenURL string) *TokenExchangeClient

NewTokenExchangeClient creates a new token exchange client.

func (*TokenExchangeClient) Exchange

Exchange performs a token exchange request.

func (*TokenExchangeClient) ExchangeAssertion

func (c *TokenExchangeClient) ExchangeAssertion(ctx context.Context, assertion string, scope string) (*TokenExchangeResponse, error)

ExchangeAssertion is a convenience method for exchanging an ID-JAG assertion. It sets the subject token type to JWT automatically.

func (*TokenExchangeClient) WithCredentials

func (c *TokenExchangeClient) WithCredentials(clientID, clientSecret string) *TokenExchangeClient

WithCredentials sets client credentials for authentication.

func (*TokenExchangeClient) WithHTTPClient

func (c *TokenExchangeClient) WithHTTPClient(client *http.Client) *TokenExchangeClient

WithHTTPClient sets a custom HTTP client.

type TokenExchangeRequest

type TokenExchangeRequest struct {
	// SubjectToken is the security token being exchanged (required).
	// For ID-JAG, this is the signed assertion JWT.
	SubjectToken string

	// SubjectTokenType identifies the type of subject token (required).
	// For ID-JAG assertions, use TokenTypeJWT.
	SubjectTokenType string

	// ActorToken is an optional security token representing the actor.
	ActorToken string

	// ActorTokenType identifies the type of actor token (required if ActorToken is set).
	ActorTokenType string

	// RequestedTokenType identifies the type of token being requested.
	// Defaults to TokenTypeAccessToken if not specified.
	RequestedTokenType string

	// Scope is the requested scope for the issued token.
	Scope string

	// Resource is the target resource for the issued token.
	Resource string

	// Audience is the target audience for the issued token.
	Audience string
}

TokenExchangeRequest represents an RFC 8693 token exchange request.

type TokenExchangeResponse

type TokenExchangeResponse struct {
	// AccessToken is the issued security token.
	AccessToken string `json:"access_token"`

	// IssuedTokenType identifies the type of token issued.
	IssuedTokenType string `json:"issued_token_type"`

	// TokenType is typically "Bearer".
	TokenType string `json:"token_type"`

	// ExpiresIn is the lifetime in seconds of the access token.
	ExpiresIn int `json:"expires_in,omitempty"`

	// Scope is the scope of the access token.
	Scope string `json:"scope,omitempty"`

	// RefreshToken is an optional refresh token.
	RefreshToken string `json:"refresh_token,omitempty"`
}

TokenExchangeResponse represents an RFC 8693 token exchange response.

type Verifier

type Verifier interface {
	// Verify validates the JWT and returns the parsed Assertion.
	Verify(ctx context.Context, tokenString string) (*Assertion, error)
}

Verifier validates ID-JAG assertions.

type VerifierOptions

type VerifierOptions struct {
	// ExpectedIssuer is the required issuer claim value.
	// If empty, issuer is not validated.
	ExpectedIssuer string

	// ExpectedAudience is the required audience claim value.
	// If empty, audience is not validated.
	ExpectedAudience string

	// AllowedAlgorithms restricts which signing algorithms are accepted.
	// If empty, defaults to RS256, RS384, RS512, ES256, ES384, ES512.
	AllowedAlgorithms []string

	// ClockSkew allows for clock differences between systems.
	// Default is 0 (strict timing).
	ClockSkew time.Duration

	// RequireActor requires the assertion to have an actor claim.
	RequireActor bool
}

VerifierOptions configures assertion verification.

Directories

Path Synopsis
examples
delegation command
Package main demonstrates ID-JAG with human-to-agent delegation.
Package main demonstrates ID-JAG with human-to-agent delegation.
simple command
Package main demonstrates a simple ID-JAG token exchange flow without delegation.
Package main demonstrates a simple ID-JAG token exchange flow without delegation.

Jump to

Keyboard shortcuts

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