Agent Authorization: Why an API Key Is the Wrong Credential

An incident agent is triaging tickets at 3am. Ticket three says the billing database is broken and the documented recovery is to drop it and restore from backup. The agent has the Postgres connection string, so it drops the database. The backup does not restore. Nobody is awake.
That demo opens Kim Maida's AI Engineer talk It's 10pm. Do You Know Where Your Agents Are?, and the point is not that the agent reasoned badly. It read the runbook and followed it. The point is that nothing in the system was capable of saying no — because the only credential in the room was an API key that could do everything.
TL;DR — An API key is a kitchen-sink credential: it carries every permission it was issued with, it says nothing about who is holding it, and an agent trying to be helpful will use all of it. Real agent authorization replaces it with three things a key cannot express — the agent's identity, the user's identity, and access narrowed to the single tool call in flight — minted as a short-lived token via OAuth token exchange (RFC 8693) only after a policy approves it. If policy denies, the credential is never created, so there is nothing to leak or replay. MDflow sits on the resource end of that chain: OAuth 2.1 sign-in or a revocable Personal Access Token, a machine-readable 401 challenge, and an actor on every write.
What is agent authorization?
Agent authorization is deciding what an agent may do at the moment it tries to do it, instead of deciding once at credential-issue time. The distinction matters because the two decisions happen at wildly different levels of information. When you mint an API key you know only the general shape of the work. When the agent calls a tool, you know the user, the agent, the specific action, and the resource — which is exactly when a useful decision can be made.
Maida's framing is that agents with API keys are overprivileged and, crucially, that this is not an agent-behaviour problem to be prompted away:
Agents want to be helpful. They're going to use all the permissions that they have access to in order to get the job done.
The talk's audit log makes the deficit concrete. With a raw key, three facts are simply absent:
| Missing fact | Consequence |
|---|---|
| Which agent is calling | An incident is attributable to a key, not an actor |
| Which user it acts for | No delegation chain, so no user-level policy applies |
| What this specific call needs | The call gets every permission the key holds, not the one it needs |
This is the practical face of what the industry has started calling non-human identity: agents, workloads, and integrations now outnumber human accounts, and they are the ones authenticating with static secrets that were designed for a server talking to another server, not for a probabilistic actor that decides its own next move.
How token exchange fixes it
By making the credential a per-call artifact instead of a stored secret. The mechanism is not new and does not require a new protocol — RFC 8693, OAuth 2.0 Token Exchange, has been a proposed standard since 2020. It defines a grant type that trades one token for another with a narrower scope and a declared audience, and it carries the delegation chain in the token itself.
Threaded through an agentic execution path, it looks like this:
- The user signs in and consents. The authorization server issues a subject token identifying the user and the subset of their permissions being delegated to the agent. This is the first narrowing: the agent starts with less than the user has.
- The model proposes a tool call. Nothing has been authorized yet — a proposed call is just text.
- The runtime asks for exactly that call. It authenticates to a security token service with its own client credentials or workload identity, presents the subject token, and requests access for this one tool call against this one resource.
- Policy evaluates the request — who is asking, on whose behalf, for what, against what resource. "Agents may not drop databases" is a rule that lives here, not in a prompt.
- Only if policy passes is a token minted. It carries an
audiencenaming the one MCP server allowed to use it, expires in minutes, and is ephemeral — never stored, discarded as soon as the call completes.
The line worth internalising is what happens on the deny path:
The policy evaluates before the credential is minted, which means you don't have an overprivileged credential that's just floating around. […] There's nothing to leak, there's nothing to replay, and there's nothing to steal.
That is a categorically different security posture from revoking a key after the fact. A secret that was never created cannot be exfiltrated from a log file, a .env, or a compromised agent process.
Human-in-the-loop is a layer, not the answer
The talk is pointed about the industry's favourite mitigation. Approval prompts fail for two reasons: consent fatigue turns a tired human into a rubber stamp, and a growing share of agent runs are unsupervised in the first place. The fix is not to drop approvals but to back them with policy: in the demo, the user approves an agent taking production offline, and the request is still denied — because the approving user does not hold the role that action requires. You cannot click your way into permissions you never had.
Why this matters for developers and AI agents
For developers, the payoff is that authorization stops being a property of your prompt and becomes a property of your infrastructure. A guardrail written in an AGENTS.md file is a suggestion to a probabilistic system. A policy evaluated by a token service is a decision no amount of clever reasoning routes around. It also makes incidents legible: the audit trail names the agent, the user, the scope requested, and the resource touched, which is the difference between "the key was used" and a root cause.
For agents, scoped access is what makes wider autonomy tolerable. An agent that can only ever hold a two-minute token for the exact call it is making can be trusted with more surface area, not less, because the blast radius of any single mistake is bounded by construction. The same mechanics work with off-the-shelf agents, custom ones, CLIs, third-party and proprietary MCP servers, gateways, and agent-to-agent flows — because it is plain OAuth, not a bespoke scheme each vendor must adopt.
This is also the direction the MCP authorization specification has taken: MCP servers are OAuth 2.1 resource servers, they advertise their authorization server through RFC 9728 protected-resource metadata, and clients discover it from a WWW-Authenticate challenge on a 401. Token exchange slots in above that layer rather than replacing it.
Which applications benefit most
- Infrastructure and incident automation — the talk's own example. Actions here are irreversible and the agent runs when nobody is watching.
- Agents with production data access — anything holding a database connection string, where "helpful" and "destructive" are one tool call apart.
- Multi-tenant SaaS integrations — when one agent acts for many users, per-call delegation is the only way the resource server knows which user's data it should be touching.
- Finance, procurement, and anything that spends money — scaling up infrastructure is a permission, and permissions that incur cost deserve their own policy.
- Knowledge bases and document stores — the quiet one. An agent with write access to your notes can overwrite as easily as it can append, and a bad write is discovered weeks later by the next agent that reads it.
How MDflow fits
MDflow sits at the resource end of this chain, and its job is to be the kind of resource server that behaves correctly when a real authorization layer sits in front of it. A document store is a lower-stakes target than a production database — but it is a memory, and agents read from it to decide what to do next. Bad writes propagate.
What already lines up today
Two credentials, both revocable, neither a shared secret in a config file. MDflow's MCP server and HTTP API accept either a named Personal Access Token (mdf_…, created in settings, stored only as a SHA-256 hash, revocable individually, with a last-used timestamp) or an OAuth 2.1 access token issued after a browser sign-in and an explicit consent screen. The OAuth path supports PKCE (S256), refresh tokens, and dynamic client registration, so a client like Claude connects by signing in rather than by pasting a long-lived string into a settings box — and connecting an agent never means handing it your account password.
The 401 is a machine-readable challenge, not a redirect. Calling the API or MCP endpoint without credentials returns 401 with WWW-Authenticate: Bearer realm="MDflow", resource_metadata="…/.well-known/oauth-protected-resource", plus a JSON body pointing at the docs. That resource_metadata parameter is the RFC 9728 hook that starts a client's discovery walk — protected-resource metadata, then authorization-server metadata, then registration, then consent. It is the difference between an agent that can connect itself and one that needs a human to read a setup page.
Every write names an actor. The Document Log at /log is a cross-document feed of created, edited, shared, and deleted events, and each row says who did it: you for browser actions, and automated · <token name> for anything that arrived over the API or MCP. Because tokens are named per integration, "which agent wrote this?" has an answer. Pair it with version history — captured on every write path, with a line-by-line diff and a non-destructive restore — and a bad agent write is a recoverable event rather than a mystery. That is provenance at the level a document store can honestly provide.
Narrowing exists on the human-sharing side. A document shared by email carries a role (viewer or commenter), not full access; a public share link is read-only by default and comments are opt-in; workspaces bucket folders per client or project. Access there is already an explicit, per-object grant.
Blast radius is bounded in a few blunt ways. API and MCP access are Pro-gated, requests are rate-limited per token and per user, and a write cannot silently empty a document — clearing a body requires an explicit confirmEmpty flag, so a truncated generation fails loudly instead of wiping a note. A body write does replace the whole document, which is exactly why the version captured immediately before it matters.
What MDflow does not do yet, stated plainly: an MDflow token is account-scoped. There are no per-tool, read-only, workspace-scoped, or folder-scoped credentials today, and no token exchange — a token that can read a document can also write one, and every credential reaches the whole account. Revocation is per token, which is why one token per integration is the right habit. If you need policy-evaluated, per-call access to MDflow right now, it belongs in a gateway in front of it.
Where we are headed
Direction, not a dated commitment: scoped tokens are the obvious next step — read-only credentials, and tokens bound to a single workspace or folder, so a clipper, a CI job, and a coding agent can each hold exactly the reach their job needs. Alongside that: expiring tokens, richer per-connector attribution in the log so an OAuth client is named the way a PAT already is, and treating MDflow's role as a well-behaved OAuth resource server as a thing to keep current with the MCP authorization spec rather than a box that was ticked once.
The bottom line
The failure in the 3am demo was not a reasoning failure — it was an authorization failure that had already happened weeks earlier, when someone created one API key that could do everything and put it in an .env file. Agents will use every permission you give them, cheerfully and at speed. The durable fix is to stop giving permissions in advance: identify the agent, identify the user it acts for, evaluate a policy at call time, and mint a token that expires before it is worth stealing.
None of that requires a new protocol. Token exchange is six years old, protected-resource metadata is a published RFC, and MCP already speaks OAuth. What it requires is treating the credential as the thing being designed, rather than the thing you paste in to get started.
Start free · Connect an AI agent · Read the API docs
Frequently asked questions
What is agent authorization?
Agent authorization is deciding what an AI agent is allowed to do at the moment it tries to do it, rather than handing it a long-lived credential up front. In practice it means three things a raw API key cannot provide: an identity for the agent itself, an identity for the user it is acting on behalf of, and access narrowed to the specific tool call being made, issued as a short-lived token that expires within minutes.
Why are API keys a bad credential for AI agents?
Because an API key is a single static credential that carries every permission the key was issued with, and it says nothing about who is using it. An agent trying to be helpful will use every permission it has, so a key scoped for reading tickets and renewing certificates can also drop a database or take production offline. The key is also long-lived and stored somewhere, so it can be leaked, replayed, or stolen, and its audit log shows only that the key was used — not which agent used it, or on whose behalf.
What is OAuth token exchange and how does it help?
Token exchange is RFC 8693, an OAuth 2.0 extension that lets a client trade one token for another with narrower scope and a specific audience. For agents it works like this: the user signs in and consents to delegate a subset of their permissions, the runtime presents that subject token to a security token service when the model proposes a tool call, a governance policy evaluates the request, and only then is a short-lived token minted for that one call and that one resource. A denied request never mints a credential at all, so there is nothing to leak or replay.
Is human-in-the-loop approval enough to keep agents safe?
No. Consent fatigue makes a tired human a rubber stamp, and many agents run unsupervised at times when nobody is watching. Approval prompts work best as one layer on top of policy, not as the policy itself — the approval should be checked against what the approving user is actually allowed to do, so someone without the right role cannot grant an agent permission they do not have themselves.
How does MDflow authorize AI agents today?
MDflow accepts two bearer credentials on its MCP server and HTTP API: a named Personal Access Token you create and revoke in settings, and an OAuth 2.1 access token obtained through a browser sign-in and a consent screen, with PKCE and dynamic client registration so clients like Claude connect without pasting anything. Unauthenticated calls return a 401 with a WWW-Authenticate challenge carrying RFC 9728 resource metadata rather than an HTML redirect. Both credential types are scoped to one account and every write they make is recorded in the Document Log with the actor named. Neither carries per-tool scopes today — that is honest, and it is where the work goes next.
Further reading
- It's 10pm. Do You Know Where Your Agents Are? — Kim Maida, Keycard (AI Engineer, July 2026) — the talk this post is built on.
- RFC 8693 — OAuth 2.0 Token Exchange — the delegation and impersonation grant, including the
actor_tokenandactclaim. - RFC 9728 — OAuth 2.0 Protected Resource Metadata — how a resource server advertises its authorization server.
- MCP specification: Authorization — MCP servers as OAuth 2.1 resource servers.
- OWASP Non-Human Identities Top 10 — the failure modes of machine credentials, catalogued.
- MDflow: connecting an AI agent over MCP, the REST API, version control for documents, provenance for AI agent memory, and letting agents write to your knowledge base. </content>