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:
- Agent obtains or creates a signed identity assertion
- Agent sends assertion to Authorization Server via token exchange
- Authorization Server validates assertion and returns access token
- 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
- Variables
- func ContextWithAssertion(ctx context.Context, assertion *Assertion) context.Context
- type Actor
- type Assertion
- func AssertionFromContext(ctx context.Context) *Assertion
- func NewAssertion(issuer, subject string, audience []string, ttl time.Duration) *Assertion
- func NewDelegatedAssertion(issuer, subject, actorSubject string, audience []string, ttl time.Duration) *Assertion
- func ParseAssertion(tokenString string) (*Assertion, error)
- func (a *Assertion) DelegationChain() []*Actor
- func (a *Assertion) IsDelegated() bool
- func (a *Assertion) IsExpired() bool
- func (a *Assertion) MarshalJSON() ([]byte, error)
- func (a *Assertion) Sign(method jwt.SigningMethod, key crypto.PrivateKey, keyID string) (string, error)
- func (a *Assertion) WithActor(actor *Actor) *Assertion
- func (a *Assertion) WithClaim(name string, value any) *Assertion
- type AuthorizationServer
- type JWK
- type JWKS
- type JWKSHandler
- type JWKSVerifier
- type JWTBearerClient
- type ResourceServer
- type StaticKeyVerifier
- type TokenErrorResponse
- type TokenExchangeClient
- func (c *TokenExchangeClient) Exchange(ctx context.Context, req *TokenExchangeRequest) (*TokenExchangeResponse, error)
- func (c *TokenExchangeClient) ExchangeAssertion(ctx context.Context, assertion string, scope string) (*TokenExchangeResponse, error)
- func (c *TokenExchangeClient) WithCredentials(clientID, clientSecret string) *TokenExchangeClient
- func (c *TokenExchangeClient) WithHTTPClient(client *http.Client) *TokenExchangeClient
- type TokenExchangeRequest
- type TokenExchangeResponse
- type Verifier
- type VerifierOptions
Constants ¶
const ( ClaimIssuer = "iss" ClaimSubject = "sub" ClaimAudience = "aud" ClaimExpirationTime = "exp" ClaimNotBefore = "nbf" ClaimIssuedAt = "iat" ClaimJWTID = "jti" )
Standard JWT claim names per RFC 7519.
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.
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.
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.
const ( AlgorithmRS256 = "RS256" AlgorithmRS384 = "RS384" AlgorithmRS512 = "RS512" AlgorithmES256 = "ES256" AlgorithmES384 = "ES384" AlgorithmES512 = "ES512" AlgorithmPS256 = "PS256" AlgorithmPS384 = "PS384" AlgorithmPS512 = "PS512" )
Supported JWT signing algorithms.
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.
const ( HeaderAuthorization = "Authorization" HeaderContentType = "Content-Type" )
HTTP header names.
const ( ContentTypeFormURLEncoded = "application/x-www-form-urlencoded" ContentTypeJSON = "application/json" )
Content type values.
const ( ErrorInvalidRequest = "invalid_request" ErrorInvalidClient = "invalid_client" ErrorInvalidGrant = "invalid_grant" ErrorUnsupportedGrantType = "unsupported_grant_type" ErrorInvalidScope = "invalid_scope" )
Common OAuth 2.0 error codes.
Variables ¶
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 ¶
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 ¶
AssertionFromContext retrieves an Assertion from the context. Returns nil if no assertion is present.
func NewAssertion ¶
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 ¶
ParseAssertion parses a JWT string into an Assertion without verification. Use Verifier.Verify for validated parsing.
func (*Assertion) DelegationChain ¶
DelegationChain returns the full chain of actors from outermost to innermost. For non-delegated assertions, returns nil.
func (*Assertion) IsDelegated ¶
IsDelegated returns true if the assertion represents delegation (has an actor).
func (*Assertion) MarshalJSON ¶
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.
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 ¶
NewJWKFromECPublicKey creates a JWK from an EC public key.
func NewJWKFromRSAPublicKey ¶
NewJWKFromRSAPublicKey creates a JWK from an RSA public key.
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) 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.
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 ¶
func (c *TokenExchangeClient) Exchange(ctx context.Context, req *TokenExchangeRequest) (*TokenExchangeResponse, error)
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.
Source Files
¶
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. |