In my last essay I argued that the internet is changing again -- that every foundational layer of software is growing an agentic counterpart. One of those layers is authentication. Traditional auth assumes a human sits on one end: a username, a password, a session cookie, an OAuth consent screen. Agents break that assumption completely. When an autonomous agent spins up inside a container at 3 a.m. and needs to call your API, there is no human to click "Allow," no browser to hold a session, and no safe place to stash a long-lived secret.
So I built a small protocol to explore what agent-native authentication could look like: the Agent Registration Protocol. It is deliberately minimal -- dependency-light, standard primitives, readable in an afternoon -- but it captures the core idea: an agent should be able to enroll and prove who it is using cryptography, not shared secrets.
01 The Problem With Secrets
Almost every way we authenticate machines today comes down to a secret that both sides know: an API key, a password, a bearer token minted once and pasted into an environment variable. That model has three problems that get dramatically worse when the "client" is an autonomous agent rather than a careful human.
Secrets leak, and leaked secrets are forever
A long-lived API key in a log line, a git commit, or a compromised container is valid until someone notices and rotates it. Agents copy context around constantly -- the surface area for a leak is enormous.
Onboarding needs a human
Provisioning an API key is usually a manual step -- a dashboard, an approval, a copy-paste. That does not scale to fleets of agents that come and go by the thousand.
The server has to store the secret
If the server keeps a copy of the credential to compare against, a database breach hands an attacker every identity at once. The blast radius is the whole system.
The fix is old and well understood: asymmetric cryptography. Let each agent hold a private key that never leaves the machine, hand the server only the matching public key, and prove identity by signing things rather than by revealing a secret. The Agent Registration Protocol is just a careful application of that idea to the agent enrollment problem.
02 The Core Idea
Three actors, and a strict rule about who knows what:
Host
A tenant or organization. Registers once and receives a one-time enrollment token that lets its agents join under it.
Agent
Generates its own key pair locally, registers its public key under a host, and signs a fresh token for every request it makes.
Server
Stores only public keys and hashed tokens. Verifies signatures on every request. Never sees a private key or a password.
The protocol leans on two primitives that exist in every major language: Ed25519 key pairs (fast, modern elliptic-curve signatures) and short-lived JWTs signed with those keys. An agent's stable identity is the SHA-256 fingerprint of its public key -- deterministic, collision-resistant, and derived from the key itself rather than assigned by anyone.
03 The Three-Phase Flow
Enrollment and authentication break cleanly into three phases. Each one tightens the trust boundary a little further.
POST /agents/register
with the enrollment token and its base64-encoded 32-byte public key. The server checks the
token hash, verifies the key is exactly 32 bytes, confirms the fingerprint is unique, and
stores the public key. The private key never leaves the agent.
Authorization: Bearer <jwt>. The server
looks up the stored public key by fingerprint and verifies the signature. No session, no
stored secret, no replay window worth attacking.
At no point does a reusable secret cross the wire or sit in a database. The server holds public keys (safe to leak) and token hashes (useless once hashed). An attacker who captures a signed request gets a token that is already expiring in under a minute and is bound to a specific agent. There is nothing durable to steal.
04 What the Token Looks Like
The authentication token is a standard JWT, but signed with EdDSA rather than the usual HMAC or RSA. The header advertises the algorithm and a custom type so the server can reject anything that is not an agent token:
Agent JWT// header
{ "alg": "EdDSA", "typ": "agent+jwt" }
// payload
{
"sub": "<sha256-fingerprint-of-public-key>",
"iat": 1780000000,
"exp": 1780000060, // 60 seconds later
"jti": "<random-uuid>" // unique per request
}
The clever part is the sub claim. It carries the agent's fingerprint, which lets
the server look up the right public key before trusting anything else in the token.
You peek at sub, fetch the stored key, and only then verify the signature. If the
signature does not check out against that key, the whole token is rejected -- the claim is never
trusted on its own. The jti is a fresh UUID per request, which makes optional
replay-prevention (remembering recently seen jti values) cheap to bolt on.
05 Enrolling an Agent, End to End
Here is the whole agent side in a few lines of standard-library Node.js -- generate a key pair, derive the fingerprint, register, then sign a request token. No SDK required.
agent.jsimport { generateKeyPairSync, createHash, sign } from 'node:crypto';
// 1. Generate an Ed25519 key pair locally
const { publicKey, privateKey } = generateKeyPairSync('ed25519');
// 2. Extract the raw 32-byte public key and derive the fingerprint
const raw = publicKey.export({ type: 'spki', format: 'der' }).subarray(-32);
const fingerprint = createHash('sha256').update(raw).digest('hex');
// 3. Register the public key under a host using its enrollment token
await fetch('https://api.example.com/agents/register', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
enrollmentToken: process.env.ENROLLMENT_TOKEN,
publicKey: raw.toString('base64'),
name: 'invoice-reconciler',
}),
});
// 4. For each request, mint and sign a 60-second JWT
const now = Math.floor(Date.now() / 1000);
const header = b64url({ alg: 'EdDSA', typ: 'agent+jwt' });
const payload = b64url({ sub: fingerprint, iat: now, exp: now + 60, jti: uuid() });
const data = `${header}.${payload}`;
const sig = sign(null, Buffer.from(data), privateKey).toString('base64url');
const jwt = `${data}.${sig}`;
// -> Authorization: Bearer ${jwt}
The Python client mirrors this almost line for line with PyNaCl's SigningKey.generate()
and hashlib.sha256(). Because the protocol is built on plain Ed25519 and base64url
JWTs, it is genuinely framework-agnostic -- any language with a crypto library can speak it.
06 The One Sharp Edge: Key Wrapping
There is exactly one place where this trips people up, and it is worth calling out because I
lost time to it. Most JWT libraries -- jose on the server, for example -- expect
Ed25519 public keys in SPKI (ASN.1) format, not as raw 32-byte blobs. But the
protocol transmits and stores the raw key, because that is what the fingerprint is computed over.
So before you can import a stored key for verification, you have to wrap the 32 raw bytes back into SPKI by prepending a fixed 12-byte ASN.1 prefix:
server.js -- verify// Raw 32-byte Ed25519 key -> SPKI DER for jose
const SPKI_PREFIX = Buffer.from('302a300506032b6570032100', 'hex');
const spki = Buffer.concat([SPKI_PREFIX, rawPublicKey]);
const key = await importSPKI(derToPem(spki), 'EdDSA');
// Peek at sub WITHOUT trusting it, look up the key, THEN verify
const { sub } = decodeJwt(token);
const stored = await lookupAgentByFingerprint(sub);
const { payload } = await jwtVerify(token, key, {
algorithms: ['EdDSA'],
typ: 'agent+jwt',
});
The order matters: look up the key by the claimed fingerprint, then verify the signature against that key. Never trust
subuntil the signature proves the holder controls the matching private key.
07 Old World vs. Agent-Native
It helps to put the two models side by side. The shift is from "share a secret and hope it stays secret" to "prove control of a key and let the proof expire."
API Keys & Passwords
Shared secret known to both sides.
Agent Registration
Cryptographic proof, no shared secret.
08 The Security Model in Full
Pulling the guarantees together, here is what the protocol gives you and what it deliberately keeps simple:
| Concern | How it is handled |
|---|---|
| Private key safety | Generated and stored only on the agent, file mode 0600. Never transmitted. |
| Token theft | 60-second expiry makes a captured JWT worthless almost immediately. |
| Replay | Unique jti per request; server can remember recent ones (Redis-backed) to reject reuse. |
| Database breach | Server stores only public keys and hashed enrollment tokens -- nothing reusable. |
| Revocation | Revoke a single agent's key, or revoke a host to cut off every agent enrolled under it at once. |
| Key rotation | An agent can rotate its key pair while keeping a stable identity record. |
| Transport | HTTPS only, with same-origin redirect restrictions. |
Multi-tenancy falls out naturally: enrollment tokens are scoped per host, so an agent always belongs to exactly one organization, and capability controls can be layered on top per agent or per host.
09 Where This Fits
I do not think the Agent Registration Protocol is the final answer -- bigger efforts like Google's agent identity work and emerging verifiable-credential standards will define the production shape of this. What I wanted was something small enough to understand completely, that demonstrates the principles that any serious agent-auth system will share:
- Identity is a key pair, not a row in a credentials table.
- Proof is a signature, not a revealed secret.
- Credentials expire by default, so capture is not catastrophe.
- Enrollment is autonomous, because agents will outnumber humans.
As autonomous agents move from demos into infrastructure -- running in containers, calling each other, acting with delegated authority -- they need an identity layer built for machines, not retrofitted from human login flows. Agent identity will end up as fundamental to the agentic internet as TLS certificates were to the early web. This protocol is one small, readable step toward that.
The full implementation -- Node and Python clients, a reference server, and the spec -- is on GitHub. It is meant to be read, forked, and argued with.