ID-JAG Secures AI Agent Authorization
- •ID-JAG limits AI agent access by exchanging identity assertions for short-lived, narrow-scope tokens
- •Go reimplementation keeps Athenz mTLS token exchange while replacing TypeScript Express protocol layer
- •Tests simulate ZTS and upstream APIs to verify grant_type, subject_token, scope, and audience
Evan Lin published a July 28 learning note on Dev.to explaining ID-JAG, or Identity Assertion JWT Authorization Grant, as a way for AI agents to access protected internal resources on behalf of users without holding broad human credentials or shared master keys. Lin says the problem has grown in the past six months as AI agents increasingly connect directly to internal systems, where prompt injection or hallucinations could make an agent call APIs the user did not intend.
ID-JAG is described as an authorization mechanism for proving that a specific user, at a specific moment, authorized an agent to do a specific task. The mechanism remains an IETF Internet-Draft rather than an official RFC, but Lin says LY Corporation, Okta, and the MCP specification have already begun using or citing it. The design builds on OAuth 2.0 Token Exchange, RFC 8693, and JWT Bearer Grant, RFC 7523, to exchange a short-lived identity assertion into access tokens with narrower scopes.
Lin contrasts ID-JAG with OAuth2 PKCE, which protects a single authorization exchange for public clients such as mobile apps through a `code_verifier` and `code_challenge` pair. ID-JAG addresses a multi-hop agent chain, such as User logs into IdP, AI Client Gateway, MCP Server, and Final Resource Server. At each hop, the caller is not the user directly, so each service must prove it is acting under delegated authority.
The example flow uses Keycloak as the identity provider and Athenz as the authorization server. A user logs into the IdP and receives an OIDC ID Token; the AI Client Gateway uses RFC 8693 Token Exchange to swap that ID Token for an ID-JAG; RFC 7523 JWT Bearer then swaps the ID-JAG for an Athenz Access Token; the AI Client calls the MCP Server; and the MCP Server performs another RFC 8693 exchange using its own mTLS (certificate-based mutual authentication) service identity before calling the final Resource Server.
The exchanged tokens usually record `sub`, the proxied user, and `act`, the agent identity actually performing the operation. Lin says this lets downstream services see that “Alice performed this operation through a certain Agent,” preserving the audit trail. The approach also avoids requiring users to click a new browser consent prompt for every service because authorization is consolidated at SSO login, after which the identity assertion can be exchanged by the authorization server.
Lin re-implemented the MCP Server from `athenz-community/id-jag-the-hard-way` in Go as `kkdai/id-jag-mcp`, under the Apache 2.0 license. The original MCP Server used TypeScript, Express, and a hand-coded JSON-RPC 2.0 protocol without the official SDK. The Go version keeps the same scope mapping and mTLS token exchange flow, replaces the protocol layer with the official `modelcontextprotocol/go-sdk`, and uses a custom mTLS client without Athenz’s official Go client library.
The Go project includes three tools: `get_k8s_docs` with `api:role.docs-getter`, `delete_k8s_doc` with `api:role.docs-deleter`, and `post_k8s_doc` with `api:role.docs-poster`. When the MCP Server receives a request, it does not forward the AI Client Gateway’s original token. It exchanges the token with Athenz ZTS for the specific scope required by the tool, so even a broader gateway token is narrowed before the upstream API receives it.
Lin says the repository is organized around `cmd/id-jag-mcp/`, `internal/config/`, `internal/athenz/`, `internal/tools/`, and `internal/server/`. Running the service requires mTLS certificates, a reachable Athenz ZTS, and an upstream API server configured through environment variables such as `UPSTREAM_BASE_URL` and `AUTHORIZATION_SERVER_URL`. The server exposes `/mcp` plus REST shortcut routes for testing with `curl`, including `GET /api/docs`, `DELETE /api/docs/5`, and `POST /api/docs`.
Testing can run without a full Athenz or Keycloak setup because the project uses `httptest` to simulate ZTS and the upstream API. Lin lists `go build ./...`, `go vet ./...`, and `go test ./...` as validation commands. Tests under `internal/athenz` check outgoing `grant_type`, `subject_token`, `scope`, and `audience` parameters, while tests under `internal/tools` verify that upstream forwarding uses the downscoped token after exchange rather than the original token.