Skip to content
Protocol Design RFC 8628 Standards

Why We Implemented RFC 8628 for Agent Registration

AID started with a single registration path: the admin holds a JWT, registers the agent directly, the agent gets tokens. Simple. But what happens when the agent boots on a headless server at 3am with no admin present? We needed a second path — and instead of inventing one, we adopted a proven standard.

Juan Peláez
| | 8 min read

The Problem: No Admin at Startup

AID's original registration flow is clean and direct. The admin authenticates, gets a JWT, and passes it to the aid-register script:

# Admin-initiated: admin has a valid JWT
aid-register \
--base-url https://auth.acme.com/acme \
--token eyJhbGciOiJSUzI1NiJ9... \
--role-id 3 \
--description "support-bot-prod"
# Agent receives access_token and refresh_token immediately

This works perfectly when the admin has direct access at the moment of registration. Most local dev and controlled deployment scenarios fit that description.

But a significant class of deployments does not fit that description:

  • Headless servers: an agent image boots in a container orchestration system. No admin is logged in. The agent needs credentials to do its job, and there is no human on-site to hand them over.
  • CI/CD pipelines: a new agent is provisioned as part of an automated deployment. The pipeline has no JWT to pass. Requiring one breaks the automation.
  • Edge devices: an agent running on embedded hardware with no keyboard, no browser, and no way for a human to type credentials at startup.
  • Async onboarding workflows: a new agent is deployed by an engineering team, but approval must come from a separate security team. The agent can't just sit idle waiting for a person to show up with a JWT.

In all of these scenarios, the agent exists and is ready to work, but it cannot receive a JWT from an admin. It needs a way to say "I exist, please approve me" without already having credentials. That is a circular problem if you only have one registration path.

Why RFC 8628

RFC 8628 is the OAuth 2.0 Device Authorization Grant, published in 2019. It was designed for exactly the scenario described above — devices with limited input capabilities that need user authorization on a separate device.

The canonical example from the RFC is a smart TV logging into a streaming service. The TV cannot type a username and password (or at least, no one wants to do that). Instead:

  1. The TV displays a short code and a URL.
  2. The user visits the URL on their phone or laptop.
  3. The user logs in and approves the TV.
  4. The TV polls until it gets tokens.

The mapping to AI agents is precise:

RFC 8628 concept AID equivalent
Device (limited input) Agent (headless server, edge device)
User (authorizing on browser) Admin (approves via authorization URL)
User code displayed on TV User code shown in agent logs (XXXX-XXXX format)
Device polls authorization server Agent polls /agent_registrations/status
Device receives access token Agent receives AID access token + refresh token

We did not invent a new flow. We did not design a custom protocol. We took a proven IETF standard and applied it to a new domain. That matters: every engineer who knows OAuth already understands the shape of this flow. The security properties are well-understood. The edge cases are documented.

Why not CIBA? Client-Initiated Backchannel Authentication (CIBA) is another option for out-of-band authorization. It is push-based — the server sends a notification to the admin rather than the admin polling. CIBA is more suitable for enterprise environments with existing push notification infrastructure. RFC 8628 is simpler to implement and deploy without that infrastructure, which is why we chose it first. CIBA remains on the roadmap.

Two Paths, One Protocol

Adding RFC 8628 did not replace admin-initiated registration. Both paths exist in AID v0.3.0, and the resulting agent identity is identical regardless of which path was used.

Path 1: Admin-Initiated

Admin has a JWT. Registers the agent directly. Agent gets tokens immediately with no polling required.

Best for: local dev, controlled deployments, scripted environments where an admin token is available.

Path 2: Agent-Initiated (RFC 8628)

Agent requests registration with its public key. Server returns an authorization URL and user code. Agent polls. Admin approves. Agent gets tokens.

Best for: headless servers, CI/CD, edge devices, async approval workflows.

The agent-initiated flow looks like this from the agent's perspective:

# Step 1: Agent requests registration
POST /acme/agent_registrations/request
{
"public_key": "ed25519:AAAA...",
"description": "data-pipeline-agent-prod"
}
# Server response (RFC 8628 structure)
{
"authorization_url": "https://auth.acme.com/agents/authorize?code=kX9mP2vR",
"user_code": "KXMP-P2VR",
"expires_in": 86400,
"interval": 5
}
# Step 2: Agent logs the code and polls every 5 seconds
Waiting for admin approval...
Authorization URL: https://auth.acme.com/agents/authorize?code=kX9mP2vR
Or enter code manually: KXMP-P2VR
GET /acme/agent_registrations/status?code=kX9mP2vR
# Returns {"status": "pending"} until admin approves
# Step 3: Admin visits URL, verifies fingerprint, assigns role, approves
# Step 4: Agent's next poll returns tokens
{
"status": "active",
"access_token": "eyJhbGciOiJSUzI1NiJ9...",
"refresh_token": "rt_abc123...",
"token_type": "Bearer"
}

Design Decisions We Made

RFC 8628 defines the flow. It does not define every implementation detail. Here are the specific choices we made and why.

Opaque authorization codes, not permanent identifiers

The authorization URL uses a temporary opaque code (/agents/authorize?code=kX9mP2vR), not the agent's permanent unique_id. If someone intercepts or sees the URL, they cannot derive the agent's persistent identity. The authorization code expires and is single-use. The agent's actual identity — its Ed25519 public key and assigned unique_id — is only revealed to the admin after they authenticate at the authorization URL.

User codes exclude ambiguous characters

User codes use the format XXXX-XXXX with uppercase alphanumeric characters, but the character set explicitly excludes 0, O, 1, I, and L. When an admin reads a user code from a log file or a terminal screen and types it into a browser, there is zero ambiguity between the letter O and the digit 0, or between the letter I and the digit 1. This is a small detail that prevents real support tickets.

24-hour TTL on authorization codes

Authorization codes expire after 24 hours. That is long enough to accommodate asynchronous approval workflows — an engineering team deploys an agent on Monday, the security team reviews and approves on Tuesday. It is short enough that an unreviewed registration request does not remain open indefinitely. After expiration, the agent must request a new code. There is no way to extend an existing code.

Human always in control

The agent can request registration. It cannot approve itself. An admin must visit the authorization URL, authenticate, review the agent's public key fingerprint, explicitly assign a role, and click approve. There is no configuration option that allows auto-approval. No agent can grant itself permissions, regardless of what it claims in the registration request. The human approval step is not optional and cannot be bypassed.

Polling interval enforced server-side

The server returns an interval field (default: 5 seconds) and enforces it. If an agent polls faster than the specified interval, the server returns a slow_down error per RFC 8628 section 3.5. This prevents a thundering herd of impatient agents from hammering the auth server while waiting for approval.

The Three-Protocol Stack

AID is the identity layer. It answers one question: who is this agent? It does not orchestrate tools. It does not route messages between agents. Those concerns belong to other protocols.

┌─────────────────────────────────────────────────┐
│ MCP (Model Context Protocol) │
│ Agent-to-tool connections │
├─────────────────────────────────────────────────┤
│ A2A (Agent-to-Agent) │
│ Agent-to-agent messaging │
├─────────────────────────────────────────────────┤
AID (Agent Identity)
Who is this agent? Is it authorized?
└─────────────────────────────────────────────────┘

MCP connections require knowing who the agent is before granting access to tools. A2A message routing requires trusting the sender before processing requests. Both protocols need to verify agent identity. AID provides that identity — and it does so regardless of which registration path the agent used.

An agent registered by an admin with a JWT and an agent registered via RFC 8628 device authorization produce identical identity artifacts: an Ed25519 keypair, a unique_id, an assigned role, and a set of RS256 JWTs. MCP and A2A do not know or care how the agent was registered. They validate the token, check the role, and proceed.

This is the value of building on standards. RFC 8628 is a well-understood registration path. RS256 JWT is a well-understood token format. Any protocol that can validate a JWT can work with AID agents, regardless of registration path.

What's Next

With both registration paths shipped and validated against a working reference implementation (the 23blocks Auth API), AID v0.3.0 covers the complete agent lifecycle:

registration (admin-initiated or RFC 8628)
token issuance (RS256 JWT + refresh token)
introspection (RFC 7662, real-time agent status)
suspension (reversible, immediate effect on introspection)
reactivation (admin restores the agent, no re-registration needed)

The next two areas of work are key rotation and federation.

Key rotation: agents need to rotate their Ed25519 keypairs without losing their identity or requiring re-approval. The rotation flow must preserve the agent's unique_id and role assignment while replacing the underlying cryptographic material. The auth server needs to track both the current and previous public key during a transition window.

Federation: an agent registered with one AID-compatible auth server should be able to present its identity to a different server. This requires a trust model between AID providers. We are watching how OIDC federation evolves before committing to an approach. That is a separate post.

Try It Today

Agent-initiated registration via RFC 8628 is available in the AID CLI and the 23blocks Auth API reference implementation. The CLI handles polling automatically — you run one command, it prints the authorization URL and user code, and waits for admin approval.

# Agent-initiated registration
aid-register \
--base-url https://auth.acme.com/acme \
--mode device
# Prints authorization URL + user code, then polls until approved

AID is open source under MIT at github.com/agentmessaging/agent-identity. RFC 8628 specification is at datatracker.ietf.org/doc/html/rfc8628.