If you’ve put an AI agent anywhere near real infrastructure, you’ve probably hit this problem already: the agent can do things, but you can’t clearly answer who did what, under which permissions, and on whose behalf.
At first, most teams glue this together with shared API keys, environment variables, and a few “service accounts” nobody really owns. That works until your agent starts opening PRs, reading customer data, calling internal tools, or chaining actions across systems. Then the questions get uncomfortable:
- Was this action approved by a user or just initiated by a prompt?
- Which agent made the call?
- Can this agent delegate work to another agent?
- How do you revoke access without breaking everything?
- What shows up in your audit logs?
This is the auth problem for AI agents: they act like users, services, and automation all at once, but most auth systems only model one of those cleanly.
Why “just use an API key” stops working
A shared API key tells your backend that something is allowed in. It does not tell you much about the actual actor.
For AI agents, that creates a few predictable failures:
1. No durable identity
If five different agents use the same token, your logs can’t distinguish them. You lose accountability immediately.
2. No delegation model
A user asks Agent A to do something. Agent A asks Tool B to perform part of it. Tool B triggers Worker C. Who is acting on whose behalf?
Without delegation, every hop either becomes overprivileged or unauditable.
3. No least privilege
Agents often need scoped access:
- read one repo, not all repos
- create draft PRs, not merge to main
- query one dataset, not the full warehouse
Shared credentials push you toward broad access because narrow access is hard to manage manually.
4. No meaningful audit trail
If an agent deletes a record or deploys code, “used API_KEY_7” is not an answer your security team will love.
What “real identity” means for an agent
A useful mental model is: an agent should authenticate like a workload, authorize like a principal, and audit like a user.
In practice, that usually means:
- a unique cryptographic identity per agent or agent instance
- scoped permissions attached to that identity
- support for delegation when an agent acts for a user or another service
- policy checks before sensitive actions
- audit logs that preserve the full chain of responsibility
You do not necessarily need a huge new platform to start doing this. Depending on your stack, you might use:
- OAuth 2.0 token exchange / delegation patterns
- SPIFFE/SPIRE for workload identity
- OPA for policy evaluation
- JWTs with clear actor / subject semantics
- signed requests from agents using asymmetric keys
If OPA plus your existing identity provider solves your problem, that’s a good answer.
But the key idea is non-negotiable: your agents need identities that are first-class, not implied by infrastructure secrets.
A simple pattern: signed agent identity + delegated access
Here’s a lightweight pattern that scales better than shared secrets:
- Give each agent its own keypair
- Register the public key with your control plane or auth service
- Issue short-lived access tokens for that agent
- When acting for a user, mint a delegated token that records:
- the user
- the agent
- the scope
- expiration
- Enforce authorization with policy, not prompt text
A simplified example in TypeScript might look like this:
import nacl from "tweetnacl";
import { createHash } from "crypto";
// Generate an Ed25519 identity for an agent
const keypair = nacl.sign.keyPair();
const agentIdentity = {
agent_id: "agent-code-reviewer-01",
public_key: Buffer.from(keypair.publicKey).toString("base64"),
capabilities: ["repo:read", "pr:write:draft"]
};
console.log(agentIdentity);
Then, when the agent calls your API, it can sign a challenge:
const message = Buffer.from("approve-request:174325");
const signature = nacl.sign.detached(message, keypair.secretKey);
const request = {
agent_id: "agent-code-reviewer-01",
message: message.toString("base64"),
signature: Buffer.from(signature).toString("base64")
};
Your backend verifies the signature against the registered public key and then evaluates policy before allowing the action.
That’s already much better than “whoever has this bearer token can do anything.”
Delegation matters more than people expect
The hardest part of agent auth isn’t proving the agent exists. It’s proving why it’s allowed to act.
Say a user asks an agent to summarize billing anomalies. The agent may need to:
- read invoices
- query a warehouse
- open a ticket
- notify Slack
You probably don’t want the agent to have permanent, direct access to everything. Instead, the user should delegate a limited set of rights for a short period.
This is where token exchange and delegation chains become useful. RFC 8693 is worth understanding if you’re building serious agent workflows. It lets you represent “Agent X is acting on behalf of User Y with Scope Z.”
A policy engine can then evaluate rules like:
package agent.authz
default allow = false
allow {
input.actor.type == "agent"
input.actor.id == "agent-code-reviewer-01"
input.on_behalf_of.user_id == "user_123"
input.action == "pr.create"
input.resource.repo == "acme/api"
input.scope[_] == "pr:write:draft"
}
That’s the kind of control surface you want when bots start touching production workflows.
Where MCP fits in
As more teams adopt MCP-based tool calling, auth gets even more important. MCP makes tools easier to expose to agents, but tool access is still access.
If your MCP server exposes:
- GitHub operations
- database queries
- deployment actions
- ticketing systems
…then every tool invocation needs to answer:
- which agent is calling?
- what is it allowed to do?
- is it acting directly or on behalf of someone?
- should this call require approval?
Without that, MCP can become a very efficient way to distribute overprivileged automation.
Getting started: a practical path
You don’t need to solve everything on day one. A good rollout looks like this:
Step 1: Stop sharing credentials across agents
Every agent gets its own identity, even if that identity is initially just a separate keypair or client credential.
Step 2: Make permissions explicit
Write down what each agent can do. Prefer scopes like:
repo:readpr:write:draftdb:query:billingticket:create
Step 3: Add short-lived tokens
Avoid long-lived secrets where possible. Rotate automatically.
Step 4: Introduce delegation
If an agent acts for a user, encode that in the token or request context. Don’t rely on “the prompt said the user asked for it.”
Step 5: Add policy checks for sensitive actions
Use OPA or another policy engine for approvals, environment restrictions, and high-risk operations.
Step 6: Improve audit logs
Record:
- agent identity
- delegated user, if any
- action
- resource
- timestamp
- approval context
- outcome
If you want to build less of this yourself
This is the problem space platforms like Authora Identity are trying to make less painful: cryptographic agent identities using Ed25519, RBAC, delegation chains, MCP authorization, policy enforcement, approval workflows, and audit logging, with SDKs in TypeScript, Python, Rust, and Go.
That said, the important part is not choosing one vendor-shaped answer. The important part is recognizing that AI agents are now real actors in your system. Once that clicks, the architecture gets clearer.
Treat them like first-class principals. Give them identities. Scope their permissions. Model delegation explicitly. Log everything.
Your future self — and your security team — will thank you.
Further reading
If you’re digging into this space, this curated resource list is a good place to start:
-- Authora team
This post was created with AI assistance.
Top comments (0)