Login Handshake Procedures for AI Agents: API Keys, OAuth, mTLS, and Token Exchange Compared
7 min read Fullmakt Team
- authentication
- credentials
- mcp
- a2a
- traceability
- governance
- agents
Ask five teams how their AI agents log in and you’ll get five different answers: a static API key baked into an environment variable, an OAuth 2.0 client-credentials grant, a device-code flow borrowed from CLI tooling, a mutual-TLS certificate, or — increasingly — a full MCP OAuth or A2A exchange. Each of these is a real, different login handshake procedure, with a different threat model and a different answer to “what does a successful login actually prove?” Most teams pick one because it was already wired up for human users, not because anyone worked out whether it fits an unattended agent making thousands of calls a day.
Here’s what each handshake proves, what it quietly doesn’t, and why — as we’ve argued across MCP confused-deputy and SSRF incidents — a clean handshake was almost never the actual failure.
Six handshakes, compared
1. Static API key. The oldest pattern: a long-lived secret, generated once, sent as a bearer token or header on every call. It proves the caller possesses the string. It proves nothing about which agent, which task, or which human authorized it, because the same key is reused across every call until someone remembers to rotate it — which is precisely the gap we’ve covered on scoped credentials and agent offboarding.
2. OAuth 2.0 client-credentials grant. The standard machine-to-machine pattern: an agent authenticates with its own client ID and secret, gets back a short-lived bearer token scoped to a defined set of permissions. This is a real improvement — the token expires, and scope is explicit rather than implicit. It’s also the mechanism behind most A2A agent-to-agent grants: it proves the caller is a provisioned agent entitled to a scope, not that any specific call within that scope should happen right now.
3. OAuth 2.0 authorization code with PKCE. The interactive flow: a human logs in once, consents to a scope, and the agent — or the MCP client acting for it — receives a token on the human’s behalf. This is what Claude Desktop-style connectors use to reach an MCP server. It proves informed human consent at grant time. It says nothing about whether the agent is still doing what the human consented to three hundred tool calls later.
4. Device authorization grant. Built for inputs that can’t run a browser — a headless agent host, a CLI, a background worker. A human approves the request on a separate device; the agent gets a token once that’s done. Useful for provisioning unattended agents, but the approval step is a point-in-time event: once granted, the token behaves exactly like any other bearer credential for as long as it’s valid.
5. Mutual TLS. Both sides present certificates; the connection itself is the proof of identity, with no bearer token to leak in a log or a prompt. Strong against credential theft, common in regulated backends — and just as silent as every other handshake on the question that actually matters: whether this call, right now, is one that should happen.
6. Token exchange (RFC 8693) and delegation chains. Used when one service needs to call a second on behalf of a caller without simply forwarding its token — increasingly relevant as agents call other agents over A2A and those agents call tools over MCP. Each hop mints a new, narrower token derived from the last. Done right, this shrinks blast radius hop by hop. Done wrong — or skipped, with the original token just passed straight through — it’s how a scope minted for one purpose ends up presented three systems downstream from where anyone can still reason about it.
What none of the six actually check
Every one of these procedures answers the same narrow question well: is the caller who it claims to be, entitled to this scope, at the moment the token was issued. None of them answer the question an agent’s actual behavior turns on: should this specific action, with these specific arguments, happen right now, given everything that’s happened in this task so far. We’ve made this point before in the context of a single protocol — MCP tool poisoning and the A2A handshake — but it holds across all six mechanisms equally: the handshake is a one-time or per-token event, and agentic risk is a per-call problem. A perfectly executed OAuth flow and a bare API key fail exactly the same way once the token is in hand and nothing downstream is still checking.
That’s also why swapping one handshake for a “more secure” one rarely closes the gap that actually produces incidents. Moving from API keys to OAuth client-credentials raises the bar for who can obtain a token. It does nothing for what a legitimately-issued token is then used to do, which is where nearly every agentic AI gone wrong story actually starts.
The business case: one policy layer behind every handshake
Fullmakt doesn’t ask an organization to standardize on one login handshake procedure — that’s usually not realistic across a stack that already has API-key integrations, OAuth-based MCP servers, and A2A agent grants coexisting. Instead, Fullmakt sits behind whichever handshake gets an agent in the door and enforces the same guarantees regardless of which one it was:
- Scoped, short-lived credentials issued per call, not per session — so it doesn’t matter whether the front door was a static key or a fresh OAuth token; what the agent actually holds for any given call is narrow and expires fast.
- A policy check on every action, independent of the handshake that granted the token. Whether the caller authenticated via mTLS or a device flow, the same per-call gate decides whether this request is allowed, denied, or routed to a human.
- A cryptographically chained audit log that records which handshake authenticated the caller, what it asked for, and what happened next — giving you one trace across every hop instead of six different log formats from six different auth mechanisms.
- A hard, non-negotiable boundary around credential setup, rotation, and approval decisions, so no login handshake — however it’s implemented — can be used to reach those operations directly.
The result: whichever handshake a given integration uses, the question that actually matters — should this call happen — gets answered the same way, every time, by something other than the token itself.
FAQ
Which login handshake is most secure for an AI agent? Among the six, OAuth client-credentials or mTLS with short-lived certificates are generally stronger than static API keys, because both avoid a long-lived reusable secret. But “most secure handshake” and “safe agent” are different questions — none of the six evaluate individual actions after the token is issued.
Does OAuth solve the problem that API keys have? Partially. OAuth’s short-lived, scoped tokens are a real improvement over a static key that never expires and can’t be scoped down after the fact. But a valid OAuth token is still just proof of entitlement to a scope — it doesn’t check whether a specific call within that scope should happen.
Why does token exchange matter for A2A and MCP chains? Because agents increasingly call other agents, which call tools. Without token exchange, the simplest implementation just forwards the original token down the chain, so a credential minted for one narrow purpose ends up valid three systems away from the context that justified it.
If we already use OAuth everywhere, do we still need a broker? Yes — a broker doesn’t replace the handshake, it picks up exactly where the handshake stops. OAuth (or mTLS, or a device flow) tells you the caller is legitimate. A broker tells you, per call, whether that legitimate caller should be allowed to do the specific thing it’s asking for, and keeps a record of the answer either way.
The handshake is table stakes — pick whichever mechanism fits your stack, and get it right. But if the login handshake is the only thing standing between an agent and your systems, you’ve built the door and left the house unwatched. What happens after the handshake is the part still worth building on purpose.