ID-JAG (Identity Assertion JWT Authorization Grant) enables secure token exchange for agent authentication and delegation. This page provides a comprehensive overview of how ID-JAG works.
ID-JAG builds on established OAuth 2.0 and JWT standards to create a flexible authentication framework for AI agents.
| Component | Role |
|---|---|
| Human User | The principal whose identity may be delegated to an agent |
| AI Agent | Software that authenticates to access protected resources |
| Identity Provider | Issues identity credentials and assertions |
| Assertion Issuer | Creates signed JWT assertions for token exchange |
| Authorization Server | Validates assertions and issues access tokens |
| Resource Server | Hosts protected APIs, validates access tokens |
| JWKS Endpoint | Distributes public keys for signature verification |
ID-JAG is gaining traction as the foundation for enterprise agent authentication. Most notably, the Model Context Protocol (MCP) has adopted ID-JAG for its Enterprise-Managed Authorization extension.
The MCP Enterprise-Managed Authorization (EMA) extension uses ID-JAG to enable:
- Centralized provisioning: Organizations control MCP server access through their identity provider
- Zero-touch setup: Users receive connected servers automatically upon first login
- Policy enforcement: Security teams manage access based on group membership, roles, and conditional access
Identity Providers:
| Provider | Status | Notes |
|---|---|---|
| Okta | Supported | First supported IdP via Cross App Access (XAA) |
Clients:
| Client | Status | Notes |
|---|---|---|
| Claude | Supported | Claude, Claude Code, and Cowork via Anthropic's shared MCP layer |
| Visual Studio Code | Supported | Native IDE integration |
MCP Servers:
| Server | Status |
|---|---|
| Asana | Supported |
| Atlassian | Supported |
| Canva | Supported |
| Figma | Supported |
| Granola | Supported |
| Linear | Supported |
| Supabase | Supported |
| Slack | In Progress |
For more details, see the MCP Enterprise-Managed Auth announcement.
Traditional OAuth 2.0 flows were designed for web applications where users directly interact with authorization servers. AI agents face unique challenges:
- Agent Identity: Agents need their own identities, distinct from users
- Delegation: Agents often act on behalf of humans with proper authorization
- Chain of Trust: Complex workflows involve multiple agents delegating to each other
- Audit Trail: Systems need to track both who authorized an action and who performed it
ID-JAG addresses these challenges by introducing the actor claim (act) for delegation while maintaining compatibility with existing OAuth infrastructure.
ID-JAG supports two primary authentication modes:
The agent authenticates as itself without any human delegation. This is suitable for:
- Autonomous agents with their own credentials
- Service-to-service communication
- Scheduled tasks and background jobs
The agent acts on behalf of a human user. The assertion contains:
sub: The human user's identityact: The agent's identity
This preserves the human's identity while allowing the agent to perform actions on their behalf.
In the simple flow, an agent authenticates using its own identity without human delegation.
The agent creates a JWT assertion claiming its identity:
{
"iss": "https://issuer.example.com",
"sub": "agent:calendar-bot",
"aud": ["https://auth.example.com"],
"iat": 1609459200,
"exp": 1609459500
}Claims explained:
| Claim | Description |
|---|---|
iss |
Issuer - who created this assertion |
sub |
Subject - the agent's identity |
aud |
Audience - the authorization server |
iat |
Issued At - when the assertion was created |
exp |
Expires - when the assertion expires (short-lived!) |
The agent signs the assertion using its private key (RS256, ES256, etc.):
signedJWT, err := assertion.Sign(jwt.SigningMethodRS256, privateKey, "key-id-1")The agent sends the signed assertion to the authorization server:
POST /token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&subject_token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
&subject_token_type=urn:ietf:params:oauth:token-type:jwt
&scope=read:calendarThe authorization server:
- Parses the JWT header to find the
kid(key ID) - Fetches the public key from the JWKS endpoint
- Verifies the signature
- Validates all claims (issuer, audience, expiration)
- Applies any additional policies
If validation succeeds, the server returns an access token:
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"issued_token_type": "urn:ietf:params:oauth:token-type:access_token"
}The agent uses the access token to call protected APIs:
GET /api/calendar HTTP/1.1
Host: resource.example.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...// Create assertion for agent-only authentication
assertion := idjag.NewAssertion(
"https://issuer.example.com",
"agent:calendar-bot",
[]string{"https://auth.example.com"},
5*time.Minute,
)
// Sign the assertion
signed, _ := assertion.Sign(jwt.SigningMethodRS256, privateKey, "key-1")
// Exchange for access token
client := idjag.NewTokenExchangeClient("https://auth.example.com/token")
resp, _ := client.ExchangeAssertion(ctx, signed, "read:calendar")
// Use the access token
fmt.Println("Token:", resp.AccessToken)In the delegation flow, an agent acts on behalf of a human user. The key difference is the act (actor) claim.
The human user authenticates with their identity provider using standard mechanisms (OAuth, OIDC, SAML, etc.).
An assertion is created that identifies:
- Subject (
sub): The human user being represented - Actor (
act): The agent performing actions
{
"iss": "https://idp.example.com",
"sub": "user:alice",
"aud": ["https://auth.example.com"],
"act": {
"sub": "agent:calendar-bot"
},
"iat": 1609459200,
"exp": 1609459500
}Key insight: The sub claim is the human's identity, preserving their authorization context. The act claim identifies who is actually making the request.
The agent exchanges the delegated assertion:
POST /token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&subject_token=eyJhbGciOiJSUzI1NiIs...
&subject_token_type=urn:ietf:params:oauth:token-type:jwt
&scope=calendar:readThe returned access token preserves the delegation context:
{
"iss": "https://auth.example.com",
"sub": "user:alice",
"act": {
"sub": "agent:calendar-bot"
},
"scope": "calendar:read",
"exp": 1609463100
}The resource server can make authorization decisions based on:
- Who is being represented (
sub: user:alice) - for data access - Who is acting (
act.sub: agent:calendar-bot) - for audit and agent-specific policies
// Create delegated assertion
assertion := idjag.NewDelegatedAssertion(
"https://idp.example.com",
"user:alice", // Human user
"agent:calendar-bot", // Acting agent
[]string{"https://auth.example.com"},
5*time.Minute,
)
// Sign and exchange
signed, _ := assertion.Sign(jwt.SigningMethodRS256, privateKey, "key-1")
client := idjag.NewTokenExchangeClient("https://auth.example.com/token")
resp, _ := client.ExchangeAssertion(ctx, signed, "calendar:read")
// Access Alice's calendar
req, _ := http.NewRequest("GET", "https://api.example.com/calendar", nil)
req.Header.Set("Authorization", "Bearer "+resp.AccessToken)ID-JAG supports nested delegation chains where multiple agents are involved. This is common in orchestration scenarios.
Consider a scenario where:
- User Bob asks an orchestrator to schedule meetings
- Orchestrator Agent breaks this into subtasks
- Worker Agent performs the actual calendar operations
The delegation chain is: user:bob → agent:orchestrator → agent:worker
{
"iss": "https://issuer.example.com",
"sub": "user:bob",
"aud": ["https://auth.example.com"],
"act": {
"sub": "agent:orchestrator",
"act": {
"sub": "agent:worker"
}
}
}In Go, you can traverse the delegation chain:
assertion := idjag.AssertionFromContext(ctx)
// Check if delegated
if assertion.IsDelegated() {
fmt.Printf("User: %s\n", assertion.Subject)
// Traverse the chain
for i, actor := range assertion.DelegationChain() {
fmt.Printf("Actor %d: %s\n", i+1, actor.Subject)
}
}Output:
User: user:bob
Actor 1: agent:orchestrator
Actor 2: agent:worker
With nested delegation, resource servers can implement policies like:
- Allow only specific chains:
user:* → agent:orchestrator → agent:worker - Limit delegation depth: Maximum 3 levels of delegation
- Restrict by actor type: Only allow trusted agent identities
- Audit complete chains: Log the full delegation path
The token exchange follows RFC 8693 with ID-JAG extensions. The following sequence diagram shows the complete flow from assertion creation to resource access.
| Parameter | Required | Description |
|---|---|---|
grant_type |
Yes | urn:ietf:params:oauth:grant-type:token-exchange |
subject_token |
Yes | The signed JWT assertion |
subject_token_type |
Yes | urn:ietf:params:oauth:token-type:jwt |
scope |
No | Requested scope |
audience |
No | Target resource server |
actor_token |
No | Separate actor assertion (alternative to act claim) |
actor_token_type |
Conditional | Required if actor_token is set |
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "calendar:read"
}| Error | Description |
|---|---|
invalid_request |
Missing or invalid parameters |
invalid_grant |
Assertion validation failed |
invalid_client |
Unknown client identity |
unauthorized_client |
Client not authorized for this grant |
invalid_scope |
Requested scope not allowed |
- Short-lived: Assertions should expire within minutes (e.g., 5 minutes)
- Single use: Consider implementing one-time-use tokens with
jticlaim
Recommended algorithms:
| Algorithm | Type | Key Size |
|---|---|---|
| RS256 | RSA | 2048+ bits |
| RS384 | RSA | 2048+ bits |
| ES256 | ECDSA | P-256 |
| ES384 | ECDSA | P-384 |
Never use: HS256 (shared secret), none (unsigned)
Always validate the aud claim to prevent token reuse attacks:
verifier := idjag.NewStaticKeyVerifier(publicKey, keyID, idjag.VerifierOptions{
ExpectedAudience: "https://auth.example.com", // Required!
})Consider implementing policies for:
- Allowed issuers: Which identity providers can issue delegated assertions
- Allowed actors: Which agents can act on behalf of users
- Delegation depth: Maximum nesting level
- Scope restrictions: Agents may have limited scope compared to users
- draft-ietf-oauth-identity-assertion-authz-grant - ID-JAG specification
- RFC 8693 - OAuth 2.0 Token Exchange
- RFC 7519 - JSON Web Token (JWT)
- RFC 7523 - JWT Bearer Assertion