The Credential Broker Is the Boundary: Just-in-Time Identity for AI Agents
Most AI agents still authenticate like scripts: put an API key in an environment variable, give the runtime a broad service account, and trust the model to choose the right tool. That arrangement is convenient until the agent is compromised, the workflow crosses a service boundary, or an operator needs to answer a basic incident question: which user approved this action, which workload executed it, and why did the downstream API accept it?
A production agent needs two identities, not one.
The first is the workload identity: cryptographic proof of which deployed runtime is making a request. The second is the delegated authority: a short-lived, audience-specific grant describing what that workload may do for a user or task. SPIFFE and SPIRE are useful for the first problem. OAuth token exchange, a credential broker, and a policy-enforcing MCP gateway are useful for the second.
The design principle is simple:
The agent proves what it is. The broker decides what it may do. The tool receives only a credential minted for itself.
That separation prevents a common failure mode in agentic systems: treating a bearer token as both identity and permission, then passing it through every service in the workflow.
Why an API key cannot represent an agent action
A static key says very little. It may identify a team, a service account, or an application, but it normally cannot express the complete action context:
- Human subject: Which user is the agent acting for?
- Workload actor: Which deployed agent, version, namespace, and environment is executing?
- Target: Which API or MCP server is the credential intended for?
- Operation: Which tool and operation are allowed?
- Task: Which run, approval, or workflow step authorized the call?
- Lifetime: When must the permission stop working?
Giving the same key to the planner, tool runner, background worker, and integration service destroys those distinctions. It also makes revocation coarse. Rotating the key may stop one compromised agent, but it may simultaneously break every unrelated workflow that shares it.
The OAuth 2.0 Security Best Current Practice recommends privilege restriction and audience restriction for access tokens. It also recommends sender-constrained tokens, such as mutual TLS or DPoP, to reduce the value of a stolen bearer token. 1
The practical consequence is that an agent should not receive a credential that is broader or longer-lived than the next action requires.
Separate bootstrap identity from action authority
A useful architecture has four layers:
- Runtime identity: SPIRE attests the workload and exposes an SVID through the SPIFFE Workload API.
- Delegation context: The incoming user or workflow token represents the subject on whose behalf work is being performed.
- Credential broker: An authorization server or internal broker evaluates subject, actor, resource, scope, task state, and policy, then mints a new token.
- Resource enforcement: The downstream API or MCP server validates that the token was issued for itself and checks the requested operation before execution.
SPIFFE is not the authorization policy engine. Its job is to provide a verifiable workload identity. SPIRE can attest running software and issue short-lived X.509 or JWT SVIDs through the Workload API. A broker can then use the SVID as proof of the acting workload during token exchange. 2 3
OAuth 2.0 Token Exchange defines the vocabulary for this handoff. The subject_token represents the party on whose behalf the request is made. The optional actor_token represents the party that is acting. The request can also name a resource, audience, and narrower scope. 4
This gives a token chain an auditable shape:
user token + portal workload SVID
-> token for agent audience
agent token + agent workload SVID
-> token for MCP server audience
MCP token
-> scoped tool call
The user remains the subject. The portal and agent remain distinct actors. Each service receives a credential intended for that service instead of inheriting a token minted for a different boundary.
The reference flow
A support agent that reads a customer profile and payment status can use this sequence:
1. The user signs in with Authorization Code + PKCE.
2. The browser calls the portal API with a token whose audience is the portal.
- The portal validates the token and obtains its JWT-SVID from the local SPIFFE Workload API.
- The portal sends the user token as
subject_tokenand its SVID asactor_tokento the authorization server. - The authorization server evaluates the portal-to-agent policy and issues an agent-audience token.
6. The portal calls the agent with that token.
7. The agent validates the token, selects a tool, and obtains its own JWT-SVID.
- The agent exchanges the agent token plus its SVID for a token whose audience is the MCP server.
- The MCP server validates issuer, signature, audience, expiry, scope, and tool policy.
- The broker or gateway records the subject, actor chain, resource, operation, decision, and result.
A 2026 CNCF walkthrough from IBM Research demonstrates this pattern with SPIRE workload attestation, Keycloak as an OAuth server, delegated user identity across nested transactions, and an MCP gateway as the policy and audit point.
The important property is not the particular vendor. It is the boundary discipline: every hop gets a new credential for the next resource.
Design the token as a capability envelope
A token does not need to contain every detail of the prompt. It should contain enough structured evidence for the resource server to make a deterministic decision.
A decoded access-token payload might look like this:
{
"iss": "https://auth.example.com",
"aud": "https://mcp.example.com",
"sub": "user-123",
"scope": "customer.profile.read customer.payment.read",
"act": {
"sub": "spiffe://prod.example/ns/support/sa/agent"
},
"task_id": "run_01JZ9V7M6P",
"grant_id": "grant_01JZ9V2T4K",
"tool": "customer.lookup",
"tool_operations": ["profile.read", "payment.read"],
"data_classification": "confidential",
"iat": 1792399000,
"exp": 1792399060
}
The act claim follows the delegation semantics defined by RFC 8693. aud ensures that a token minted for the MCP server cannot be reused at the billing API. The scope and tool-operation fields give the resource server a machine-checkable least-privilege boundary.
The task_id and grant_id are application claims, not universal OAuth requirements. They make logs joinable without placing the entire prompt or sensitive input into the token. Keep content classification separate from authentication, but carry the result of a policy decision when downstream enforcement needs it.
Mapki micro-card — token contract
- Subject: The human or workflow principal being represented.
- Actor: The attested workload that is currently acting.
- Audience: One resource server or a deliberately small set.
- Scope: The minimum permissions needed for this step.
- Task: A bounded run or approval context.
- Expiry: Short enough to limit replay and long enough for one step.
- Evidence: A correlation ID for policy and audit records, not raw secrets.
Implement token exchange as a brokered operation
The broker should be the only component allowed to convert a workload identity into an external API credential. The agent should not know a client secret for every downstream system.
The request below uses the RFC 8693 extension grant. In a real system, the broker must authenticate the calling workload, validate the SVID issuer and audience, and apply policy before it considers the requested scope.
from urllib.parse import urlencode
import httpx
TOKEN_EXCHANGE = "urn:ietf:params:oauth:grant-type:token-exchange"
ACCESS_TOKEN = "urn:ietf:params:oauth:token-type:access_token"
JWT_TOKEN = "urn:ietf:params:oauth:token-type:jwt"
async def exchange_for_mcp_token(
token_endpoint: str,
subject_token: str,
workload_jwt_svid: str,
requested_scope: str,
mcp_resource: str,
) -> str:
form = urlencode({
"grant_type": TOKEN_EXCHANGE,
"subject_token": subject_token,
"subject_token_type": ACCESS_TOKEN,
"actor_token": workload_jwt_svid,
"actor_token_type": JWT_TOKEN,
"resource": mcp_resource,
"scope": requested_scope,
})
async with httpx.AsyncClient(timeout=10) as client:
response = await client.post(
token_endpoint,
content=form,
headers={
"content-type": "application/x-www-form-urlencoded",
"accept": "application/json",
},
)
response.raise_for_status()
body = response.json()
if body.get("token_type", "Bearer") != "Bearer":
raise ValueError("Unexpected token type")
return body["access_token"]
A broker should reject the request when the workload is not allowed to act for the subject, the requested resource is not registered, the requested scope exceeds the subject’s grant, or the SVID audience is wrong. It should also refuse a token exchange from an unauthenticated client. RFC 8693 explicitly warns that omitting client authentication can let a compromised token be leveraged into additional tokens. 4
Mapki micro-card — broker decision
- Authenticate: Verify the workload proof, not only the bearer subject token.
- Authorize: Evaluate the subject, actor, resource, scope, task, and approval state.
- Attenuate: Remove scopes that are not needed for the next step.
- Mint: Issue a new audience-restricted credential.
- Record: Log the decision and identifiers, never the token value.
- Revoke: Make the grant fail on the next broker request after revocation.
Retrieve workload identity without copying keys into the pod
The agent should request an SVID from the local Workload API rather than reading a private key or long-lived secret from an environment variable. The SPIFFE Workload API supports streaming responses so clients can receive updated identity material as rotations or revocations occur. A JWT-SVID request includes the intended audience. 3
The exact client library differs by language, but the lifecycle should follow this pattern:
connect to local Workload API
-> request JWT-SVID for auth.example.com
-> validate and use it as actor proof
-> keep the stream open for updates
-> stop using redacted or revoked SVIDs
-> retry with bounded backoff
A Kubernetes registration should bind identity to deployment facts rather than a pod name that can be recreated:
apiVersion: spire.spiffe.io/v1alpha1
kind: ClusterSPIFFEID
metadata:
name: support-agent
spec:
spiffeIDTemplate: "spiffe://prod.mapki.in/ns/{{.PodMeta.Namespace}}/sa/{{.PodSpec.ServiceAccountName}}/support-agent"
podSelector:
matchLabels:
app.kubernetes.io/name: support-agent
namespaceSelector:
matchLabels:
mapki.in/trust-domain: production
workloadSelectorTemplates:
- "k8s:ns:{{.PodMeta.Namespace}}"
- "k8s:sa:{{.PodSpec.ServiceAccountName}}"
The selector is only one layer. The authorization server must still map the resulting SPIFFE ID to an allowed actor role, registered resource, and scope policy. Workload identity proves the runtime; it does not grant the runtime unlimited access.
Enforce the boundary at the MCP server
MCP’s authorization specification requires resource indicators and says that servers must validate tokens intended specifically for the MCP server. Its security guidance explicitly forbids token passthrough: accepting an upstream token and forwarding it to a downstream API bypasses audience, audit, and trust-boundary controls. 6 7
The MCP resource server should validate at least:
type DecisionInput = {
issuer: string;
audience: string | string[];
subject: string;
actorSpiffeId?: string;
scopes: string[];
toolName: string;
operation: string;
taskId?: string;
expiresAt: number;
};
function authorizeToolCall(input: DecisionInput): void {
if (input.issuer !== "https://auth.example.com") {
throw new Error("untrusted issuer");
}
const audiences = Array.isArray(input.audience)
? input.audience
: [input.audience];
if (!audiences.includes("https://mcp.example.com")) {
throw new Error("wrong audience");
}
if (input.expiresAt <= Math.floor(Date.now() / 1000)) {
throw new Error("expired token");
}
const required = `${input.toolName}.${input.operation}`;
if (!input.scopes.includes(required)) {
throw new Error("operation is outside the granted scope");
}
if (!input.actorSpiffeId?.startsWith("spiffe://prod.mapki.in/")) {
throw new Error("unrecognized workload actor");
}
}
This example is intentionally mechanical. A model should not be able to widen the scope by emitting a different tool name. The gateway should derive the required permission from the server-side tool registry, not trust a permission string supplied in the tool arguments.
If the MCP server calls another provider, it must obtain a separate downstream token. It should never forward the token received from the MCP client. For OAuth discovery, protect against malicious metadata and SSRF: an MCP server can influence metadata URLs, so clients need allowlists, network egress controls, and private-address blocking where appropriate. 7
Add replay resistance where the threat model needs it
Short expiry limits the replay window, but it does not prove that the presenter is the original holder. For higher-risk APIs, use sender-constrained access tokens.
DPoP binds a proof JWT to the HTTP method and target URI. The proof includes a unique jti, htm, htu, iat, and, when paired with an access token, an ath hash of that token. The resource server validates the signature, request binding, freshness, token hash, and key binding. 8
Use mTLS when the service-to-service network already supports certificate identity and key custody. Use DPoP when an application-level proof is easier to deploy. Neither mechanism replaces scope checks or tool authorization; both reduce the value of a copied bearer token.
Operational failure modes are part of the design
Dynamic credentials improve containment but add real day-two work. Token expiry can happen during a retry. Clock skew can make a valid token look expired. A broker outage can block a workflow. A refresh attempt can race with revocation. A tool call can succeed while the response is lost, causing the agent to retry.
GitGuardian’s production trade-off guide recommends tying TTL to agent behavior and privilege, issuing credentials per task where possible, instrumenting issuance and expiry, and treating long-lived credentials as explicit governed exceptions. 9
Use these controls:
- Refresh before expiry with a safety window, but never extend a grant beyond its task deadline.
- Cache tokens only inside the worker that owns the task, with an upper bound shorter than the token lifetime.
3. Add an idempotency key to every mutating tool call.
4. Store issuance, denial, revocation, and expiry events in an audit stream.
- Redact access tokens, SVIDs, authorization headers, and private key material from logs.
6. Fail closed when the broker cannot determine the audience or current grant.
7. Reconcile ambiguous writes before retrying a timed-out request.
- Drill revocation and broker recovery before calling the system production-ready.
Mapki micro-card — reliability with ephemeral access
- Expiry window: Refresh early, but respect the task deadline.
- Broker outage: Deny new authority; do not fall back to a static key silently.
- Timeout: Reconcile the external operation before retrying.
- Revocation: Stop using the grant on the next authorization decision.
- Audit: Record decisions and correlation IDs, never raw credentials.
- Exception: Long-lived credentials require an owner, scope, expiry review, and compensating monitoring.
A rollout plan for an existing agent platform
- Inventory every credential available to agent pods, workers, MCP servers, and integration services.
- Identify keys reused across workflows or environments and classify their current privileges.
- Introduce workload identity for one low-risk agent and expose only the local Workload API.
- Put a broker in front of one downstream API and issue audience-specific tokens.
- Add subject and actor claims to audit events without copying prompt contents.
- Move one read-only tool to per-operation scopes and enforce them in the resource server.
- Add token exchange at the next service boundary instead of forwarding the original token.
- Test expired, revoked, wrong-audience, wrong-actor, replayed, and over-scoped requests.
- Migrate mutating tools only after idempotency and reconciliation are in place.
- Keep static credentials as visible, time-bounded exceptions until the last integration is migrated.
The success metric is not “the agent has no secrets.” The success metric is that every credential the agent can use is explainable, bounded, revocable, audience-specific, and tied to a workload that can be attested.
A useful credential broker is deliberately boring. It does not ask the language model to understand OAuth. It does not pass a user token through five services. It does not hide a global key inside an MCP server. It turns a verified workload, a delegated subject, a resource, and a tool operation into one narrow decision.
That is the boundary autonomous systems need: identity proves the actor, policy grants the action, and the downstream resource enforces the result.
References & Community Insights
Production checklist
- The workload obtains identity from SPIFFE/SPIRE or an equivalent attested identity plane.
2. The agent never receives a broad, long-lived provider key by default.
- Token exchange preserves the delegated subject while identifying the acting workload.
4. Every service boundary receives a new token for its own audience.
- Scopes are derived from the registered tool operation, not from model-generated text.
- MCP servers reject wrong-audience tokens and never perform token passthrough.
- Short-lived credentials have bounded caching, refresh, revocation, and recovery behavior.
8. High-risk APIs use mTLS or DPoP where appropriate.
9. Mutating tools use idempotency keys and reconciliation before retry.
- Audit events connect user, workload, task, grant, resource, decision, and outcome without logging secrets.

