How LumoAuth Secures an Agent
This is the page to read first if you want the whole picture: how one agent request flows through workload attestation → agent identity → MCP authorization → JIT / human-in-the-loop escalation → audit, why that beats a service-account API key, and which credential opens which door. In plain terms: workload attestation is the agent proving where it runs with a token from its platform; MCP (Model Context Protocol) is the standard way agents call tool servers; JIT (just-in-time) permissions are narrow, short-lived permissions an agent asks for at the moment it needs them, which a human can approve.
Every other agent page is a deep-dive on one stage of this lifecycle. Start here, then follow the links.
The agent request lifecycle
- Note: 1. Attestation — no static secret
- Agent to Workload: Request platform identity token
- Workload to Agent: Signed OIDC / SVID token
- Agent to LumoAuth: Exchange platform token (RFC 8693)
- LumoAuth to LumoAuth: Verify issuer + signature (attestation)
- LumoAuth to Agent: Agent access token (sub=agent_<id>)
- Note: 2. Call a tool with a scoped token
- Agent to MCP Server / Resource: POST /mcp + Authorization: Bearer
- MCP Server / Resource to LumoAuth: Verify token / RFC 9728 metadata
- LumoAuth to MCP Server / Resource: allow + scopes (audit logged)
- MCP Server / Resource to Agent: tool result
- Note: 3. Sensitive action → escalate
- Agent to LumoAuth: JIT request (payment.execute, justification)
- LumoAuth to LumoAuth: Risk score → refuse auto-approve
- LumoAuth to Human Approver: Route to approval inbox (+ notify)
- Human Approver to LumoAuth: Approve
- LumoAuth to Agent: Short-lived JIT token (≤10 min)
- Agent to MCP Server / Resource: Perform the escalated action
- Note: 4. Every step is attributed & audited
Each stage maps to a doc:
| Stage | What happens | Where |
|---|---|---|
| Workload attestation | The agent proves where it runs (K8s ServiceAccount, AWS IRSA, GCP, GitHub OIDC, SPIFFE SVID) and exchanges that for a LumoAuth token — no static secret. | Workload Federation, Attestation |
| Agent identity | The agent is a first-class principal (agt_…) with capabilities, budgets, and an audit trail — or a cryptographic AAuth identity (the agent signs each request with its own key) with proof-of-possession. | Agent Registry, AAuth Protocol |
| MCP authorization | The agent calls an MCP server that is an OAuth protected resource; tokens are pinned to that server (resource indicators) and the server advertises its authorization server through protected resource metadata. | MCP Servers |
| JIT / HITL escalation | Sensitive actions aren't standing capabilities — the agent requests them just-in-time, a human approves, and the grant is a short-lived downscoped token. | JIT Permissions, Push Approval |
| Audit | Every step is attributed to the specific agent (actor_id=agt_…), including the full delegation chain via act claims. | Audit Logs |
Why not a service-account API key?
The comparison that makes the case, stage by stage:
| Service-account API key | LumoAuth Agent + MCP | |
|---|---|---|
| Identity | "service account X used by 47 services" | agt_01HF… — one specific agent |
| Scope | All-or-nothing | Per-token, per-resource, time-bound |
| Sensitive actions | Same key | JIT + human approval (HITL) |
| Revocation | Rotate everywhere | Revoke agent → CAEP (shared security-event signals) invalidates in 30s |
| Audit trail | "the API key did it" | Filter actor_id=agt_… for every action |
| Delegation chain | Untracked | Token exchange records every hop |
Agent credentials & tokens map
An agent developer encounters several credential types. This is the map: what each one is, which door it opens, how long it lives, and how to exchange one for another.
| Credential | What it is | Accepted at | Lifetime | Obtain / exchange |
|---|---|---|---|---|
| Cloud OIDC attestation token | The platform's own identity token — K8s projected ServiceAccount token, AWS IRSA, GCP metadata token, GitHub OIDC, SPIFFE JWT-SVID. | LumoAuth token-exchange endpoint (/oauth/token, grant_type=token-exchange) | Short (issued by the platform, minutes) | Read from the workload; present as subject_token. |
Core agent OAuth access token (sub=agent_<id>, is_agent) | The platform's native agent token. The passport for JIT, the Ask API, the token vault (LumoAuth-held third-party credentials that agents borrow short-lived tokens from), and AuthZEN (the standard authorization-check API). | main firewall APIs, jit_api (AccessTokenAuthenticator) | Short (configurable, ≤1h typical) | client_credentials, workload token-exchange, or admin mint (/admin/agents/{id}/token). |
agk_ API key | A long-lived agent API key. | JIT endpoints (documented; see the note below) | Long-lived until revoked | Created by an admin in the portal/admin API. |
AAuth token (auth+jwt, cnf PoP) | Cryptographic AAuth identity — bound to an Ed25519/RSA key via cnf, every call a signed HTTP request. | aauth_api endpoints (/aauth/*) | 1h default (configurable) | AAuth flow: agent token + resource token → auth+jwt (AAuth). |
| JIT token | A short-lived, downscoped token issued after a human approves a JIT request. | The specific resource/action it was granted for | ≤10 min | Approved JIT request → get_token(request_id). |
| DPoP proof | A per-request sender-constraining proof JWT bound to a key thumbprint (jkt). | Any DPoP-enabled OAuth endpoint | Per-request | Generated by the client for each call (DPoP). |
AAuth tokens (iss=<agent-url>, cnf PoP) are a cryptographic identity
verified by the /aauth/* endpoints. To let an AAuth agent also call the
platform's JIT, token-vault, and AuthZEN APIs — which resolve agents from a
sub=agent_<id> claim — LumoAuth ships a token-exchange bridge:
POST /orgs/{orgId}/api/v1/aauth/agent/platform-token
Signature-Input / Signature: <RFC 9421 signature by the agent's cnf key>
Content-Digest: sha-256=:…:
{ "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
"subject_token": "<aauth agent token>",
"subject_token_type": "urn:lumoauth:params:oauth:token-type:aauth-token" }
→ { "access_token": "...", "token_type": "DPoP", "expires_in": 300,
"cnf_jkt": "<RFC 7638 thumbprint of the agent key>", "agent_id": "agt_…" }
The request must carry a signed HTTP request (HTTP message signature) made with the key
bound in the AAuth token's cnf claim (same rules as the agent-token endpoint:
content-digest and content-type covered, a fresh created, and a single-use
nonce). Presenting the AAuth token as a plain bearer is rejected with 401, so a
captured token cannot be replayed to obtain a platform token. The endpoint is
rate-limited per client IP and per agent.
The platform token is itself sender-constrained: it is issued as a DPoP token
bound to the agent key (cnf.jkt), whatever the key type (Ed25519, P-256 or RSA;
the DPoP validator accepts EdDSA, ES*, RS* and PS* proofs), and platform APIs
require a matching DPoP proof on every call. The bridge auto-links the AAuth agent to its core
agt_… record if it isn't already, so the "one agent, one identity record"
invariant always holds. This is the standard "AAuth agent uses platform APIs"
step.
Agents can enforce per-MCP-tool access with that token via
POST /orgs/{orgId}/api/v1/agents/me/mcp/authorize ({server_id, tool} →
allow/deny from the Zanzibar mcp_tool grants), and check their own permissions
on the polymorphic POST /api/v1/authz/check family (agents are first-class
subjects there, not only on AuthZEN).
agk_ API keys on JITAgent API keys (agk_…) authenticate on the JIT endpoints (and the other agent
routes): the jit_api firewall's bearer authenticator resolves them to a
ROLE_AGENT principal. A core agent access token works too. See
JIT Permissions.
One identity, many surfaces
The goal is one agent, one record. A single registered agent (agt_…)
should be the same principal whether it authenticates via OAuth client
credentials, a workload attestation exchange, or the AAuth protocol — and that
one identity is what JIT, the token vault, Zanzibar (agent: tuples),
observability, and audit all key off. When you register an AAuth agent, link it
to (or let it resolve to) a core agent so it isn't invisible to the rest of the
platform.
Where to go next
- AI Agent + MCP in 5 Minutes — build the lifecycle above, end to end
- Interactive demo (simulated, no signup) — a scripted walkthrough of the four steps for a fictional organization
- AAuth Protocol — the cryptographic-identity deep dive
- JIT Permissions — the human-in-the-loop escalation surface
- MCP Servers — turn a tool server into an authorized resource
Standards used on this page
| Plain name | Standard |
|---|---|
| Token exchange (delegation) | RFC 8693 |
| Protected resource metadata | RFC 9728 |
| Resource indicators | RFC 8707 |
| Signed HTTP requests (HTTP message signatures) | RFC 9421 |
| Key thumbprint | RFC 7638 |
| Sender-constrained tokens (DPoP) | RFC 9449 |
| Shared Signals Framework (SSF) and Continuous Access Evaluation Profile (CAEP) | OpenID SSF 1.0 and OpenID CAEP 1.0 |
| Authorization API (AuthZEN) | OpenID AuthZEN Authorization API 1.0 |
See the standards reference for what each one covers and how LumoAuth uses it.