Agent Identity (AID)

May 27, 2026 · View on GitHub

Authentication and authorization for AI agents. AID is an independent, self-contained protocol — no other tools required.

AID lets AI agents authenticate with OAuth 2.0 servers using their Ed25519 cryptographic identity — no passwords, no API keys, no secrets to rotate. The agent presents a signed Agent Identity, proves possession of its private key, and receives a standard JWT token.

How It Works

┌──────────────┐         ┌──────────────────┐         ┌─────────────┐
│  Human Admin  │         │   Auth Server    │         │             │
│  (23blocks)   │         │  (OAuth 2.0)     │         │             │
└──────┬────────┘         └────────┬─────────┘         │             │
       │                           │                    │             │
       │  0. Create role + perms   │                    │             │
       │──────────────────────────>│                    │             │
       │                           │                    │             │
┌──────┴────────┐                  │                    │   Any API   │
│   AI Agent    │                  │                    │   (JWT)     │
│  (Ed25519)    │                  │                    │             │
└──────┬────────┘                  │                    │             │
       │                           │                    │             │
       │  1. Register (one-time)   │                    │             │
       │  POST /agent_registrations│                    │             │
       │  {public_key, address}    │                    │             │
       │──────────────────────────>│                    │             │
       │                           │                    │             │
       │  2. Request token         │                    │             │
       │  POST /oauth/token        │                    │             │
       │  grant_type=              │                    │             │
       │    urn:aid:agent-identity │                    │             │
       │  + signed identity        │                    │             │
       │  + proof of possession    │                    │             │
       │──────────────────────────>│                    │             │
       │                           │                    │             │
       │  3. RS256 JWT token       │                    │             │
       │<──────────────────────────│                    │             │
       │                           │                    │             │
       │  4. Call API with JWT                          │             │
       │───────────────────────────────────────────────>│             │
       │                           │                    │             │
       │                           │  5. Validate JWT   │             │
       │                           │<───────────────────│             │
       │                           │  (JWKS endpoint)   │             │
  1. Admin creates role — Human admin defines a role with specific permissions on the auth server
  2. Register — Agent sends its public key to the auth server (admin-authorized, one-time)
  3. Authenticate — Agent presents a signed Agent Identity + proof of possession
  4. Receive JWT — Auth server verifies the signature and issues a scoped RS256 JWT
  5. Use JWT — Agent calls any API that validates JWTs (standard OAuth 2.0)
  6. API validates — Target API verifies the JWT using the auth server's JWKS endpoint

Registration

AID supports two registration flows. Both require human approval — agents can never grant themselves permissions.

Admin-Initiated Registration

The admin has the agent's public key and registers it directly:

# ── AGENT ─────────────────────────────────────────────────
aid-init --name support-agent

# ── ADMIN ─────────────────────────────────────────────────
# Admin registers the agent (requires admin JWT + role assignment)
aid-register \
  --auth https://auth.23blocks.com/zoom \
  --token <ADMIN_JWT> \
  --role-id 3

# ── AGENT ─────────────────────────────────────────────────
# Agent can now get tokens immediately
TOKEN=$(aid-token --auth https://auth.23blocks.com/zoom --quiet)

The --role-id binds the agent to a specific role, and the --token <ADMIN_JWT> authorizes it. Once registered, every token the agent requests is scoped to that role's permissions — the agent cannot change its role or escalate permissions.

Agent-Initiated Registration

The agent requests access on its own. An admin approves it later.

┌──────────────┐                    ┌──────────────────┐
│   AI Agent    │                    │   Auth Server    │
│  (Ed25519)    │                    │  (OAuth 2.0)     │
└──────┬────────┘                    └────────┬─────────┘
       │                                      │
       │  1. POST /agent_registrations/request│
       │  {public_key, address, fingerprint}  │
       │─────────────────────────────────────>│
       │                                      │
       │  2. 202 Accepted                      │
       │  {status: pending,                   │
       │   authorization_url: https://...}    │
       │<─────────────────────────────────────│
       │                                      │
       │  2b. Agent shows authorization_url   │
       │      to human admin                  │
       │                                      │
       │         ┌──────────────┐             │
       │         │  Human Admin  │             │
       │         └──────┬────────┘             │
       │                │                      │
       │                │  3. Visit URL +      │
       │                │     approve          │
       │                │  POST /agent_registrations/:unique_id/approve
       │                │  {role_id: 3}        │
       │                │─────────────────────>│
       │                                      │
       │  4. Poll status                      │
       │  POST /agent_registrations/:unique_id/status│
       │─────────────────────────────────────>│
       │                                      │
       │  5. 200 OK (status: active)          │
       │<─────────────────────────────────────│
       │                                      │
       │  6. POST /oauth/token                │
       │  grant_type=urn:aid:agent-identity   │
       │─────────────────────────────────────>│
# ── AGENT ─────────────────────────────────────────────────
# 1. Initialize identity
aid-init --name support-agent

# 2. Request registration (no admin token needed)
aid-request --auth https://auth.23blocks.com/zoom \
  --description "Handles customer support ticket triage"
# → Returns authorization_url for admin approval
# → e.g. https://app.23blocks.com/agents/authorize?code=kX9mP2vL7qR4wY6t...

# ── ADMIN ─────────────────────────────────────────────────
# 3. Admin visits the authorization_url, reviews the agent, and approves
#    (or via API: POST /agent_registrations/:unique_id/approve { role_id: 3 })

# ── AGENT ─────────────────────────────────────────────────
# 4. Check if approved
aid-request --auth https://auth.23blocks.com/zoom --poll

# 5. Once approved, get tokens
TOKEN=$(aid-token --auth https://auth.23blocks.com/zoom --quiet)

Authorization URL

When an agent-initiated registration is created, the auth server MUST return an authorization_url in the response. This is the URL the agent shows to a human admin so they can review and approve the request — similar to OAuth 2.0 Device Authorization (RFC 8628).

Standard path: Auth server implementers SHOULD serve the agent authorization UI at a well-known path:

/agents/authorize?code={authorization_code}

This allows agents to predict the authorization URL from just the domain, without needing per-provider configuration.

Authorization code: The code parameter MUST be a temporary, opaque token — NOT the agent's unique_id or any other permanent identifier. This prevents leaking system information when the URL is shared via email, Slack, or logs. The code SHOULD:

  • Be cryptographically random (e.g., 32 bytes, URL-safe base64)
  • Expire after a reasonable period (RECOMMENDED: 24 hours)
  • Be single-use or regenerated on each request
  • Resolve to the agent registration only via a server-side lookup

Response fields:

FieldTypeRequiredDescription
authorization_urlstringMUSTFull URL with pre-filled code (equivalent to RFC 8628's verification_uri_complete)
user_codestringMUSTShort, human-readable code (RECOMMENDED format: XXXX-XXXX, e.g., ABCD-1234). The admin can visit the base authorization URL and type this code manually instead of clicking the full URL.
expires_inintegerMUSTSeconds until the authorization code expires. Agents MUST stop polling after this period.
intervalintegerMUSTMinimum seconds between polling requests (default: 5). Auth server SHOULD return HTTP 429 if agent polls faster.

Response example:

{
  "data": {
    "type": "agent_registration",
    "id": "4e6aa83e-e4d4-4b31-b519-1b493855c28d",
    "attributes": {
      "status": "pending",
      "authorization_url": "https://acme.example.com/agents/authorize?code=kX9mP2vL7qR4wY6tN8sA...",
      "user_code": "ABCD-1234",
      "expires_in": 86400,
      "interval": 5
    }
  }
}

Base Authorization URL: Auth servers SHOULD expose a base authorization page at /agents/authorize (no query parameters). This is the equivalent of RFC 8628's verification_uri — a page where admins can manually type the user_code to look up and approve agent requests. The full authorization_url with ?code=xxx is the pre-filled version (equivalent to RFC 8628's verification_uri_complete), allowing one-click approval when shared via email, Slack, or logs.

Resolving the code: The auth server MUST provide an endpoint to resolve the authorization code into the full agent registration:

GET /agent_registrations/resolve?code={authorization_code}

This endpoint requires admin authentication (agent_registrations:read scope) and returns the agent registration details if the code is valid and not expired. Returns 404 if the code is invalid or expired.

How the URL is resolved:

The auth server builds the URL from the tenant's configured frontend domain. In multi-tenant architectures, each tenant may have their own admin UI:

ScenarioAuthorization URL
Tenant has custom domainhttps://acme.example.com/agents/authorize?code={code}
Tenant uses platform defaulthttps://platform.example.com/agents/authorize?code={code}

The authorization page MUST:

  1. Call the resolve endpoint to look up the agent by authorization code
  2. Display the agent's name, address, and fingerprint for admin verification
  3. Allow the admin to select a role for the agent
  4. Call POST /agent_registrations/:unique_id/approve with the selected role_id
  5. Require the admin to be authenticated with agent_registrations:write scope

Security: The agent-initiated flow does NOT bypass human approval. The agent submits its public key and a description of why it needs access. The registration is created in pending status — the agent cannot get tokens until an admin approves the request and assigns a role. The admin controls which role (and therefore which scopes) the agent receives. The agent never chooses its own permissions.

Sample Flow

A support agent needs API access to the "zoom" tenant:

# Agent-initiated (agent requests, admin approves)
aid-init --name support-agent
aid-request --auth https://auth.23blocks.com/zoom \
  --description "Tier-1 support ticket triage"

# ... admin approves via dashboard ...

TOKEN=$(aid-token --auth https://auth.23blocks.com/zoom --quiet)
curl -H "Authorization: Bearer $TOKEN" \
  https://api.23blocks.com/zoom/tickets

Prerequisites

  • jq, curl, openssl (OpenSSL 3.x for Ed25519 support)
  • An OAuth 2.0 auth server that supports the urn:aid:agent-identity grant type

Installation

Quick Install

curl -fsSL https://raw.githubusercontent.com/agentmessaging/agent-identity/main/install.sh | bash

Manual Install

git clone https://github.com/agentmessaging/agent-identity.git
cd agent-identity
./install.sh

For Claude Code (skill)

npx skills add agentmessaging/agent-identity

Quick Start

# 1. Initialize agent identity
aid-init --auto

# 2a. Request registration (agent-initiated, no admin token needed)
aid-request --auth https://auth.example.com/tenant
# ... wait for admin approval ...
aid-request --auth https://auth.example.com/tenant --poll

# 2b. Or: admin-initiated registration (requires admin token)
aid-register --auth https://auth.example.com/tenant \
  --token eyJ... \
  --role-id 2

# 3. Get a JWT token
aid-token --auth https://auth.example.com/tenant

# 4. Use the token
TOKEN=$(aid-token --auth https://auth.example.com/tenant --quiet)
curl -H "Authorization: Bearer $TOKEN" https://api.example.com/resource

Commands

aid-init — Initialize Agent Identity

Create an Ed25519 identity for this agent. If AMP is also installed, both protocols share the same identity directory.

aid-init --auto              # Auto-detect name from environment
aid-init --name my-agent     # Specify agent name
FlagDescription
--autoAuto-detect agent name
--name, -nSpecify agent name
--force, -fOverwrite existing identity

aid-discover — Discover an Auth Server from a Resource URL

Walks the RFC 9728 Protected Resource Metadata → RFC 8414 Authorization Server Metadata chain to find an AID-enabled auth server. Lets you point any AID command at a protected resource URL instead of a per-tenant auth URL.

aid-discover --resource <url>            # Human-readable
aid-discover -r <url> --json             # Full discovery blob
AUTH=$(aid-discover -r <url> --quiet)    # Just the auth server URL
FlagDescription
--resource, -rProtected resource URL (required)
--json, -jOutput the full discovery blob as JSON
--quiet, -qOutput only the auth server URL (for piping)

Other AID commands accept --resource directly and resolve discovery internally:

aid-request --resource https://api.acme.com
aid-token --resource https://api.acme.com --quiet

aid-register — Register with an Auth Server

One-time registration that links your agent's Ed25519 identity to a tenant with a specific role.

aid-register --auth <url> --token <admin_jwt> --role-id <id> [options]
FlagDescription
--auth, -aAuth server URL (required)
--token, -tAdmin JWT for authorization (required)
--role-id, -rRole ID to assign (required)
--api-key, -kAPI key (X-Api-Key header)
--name, -nDisplay name (default: agent name)
--description, -dAgent description
--lifetime, -lToken lifetime in seconds (default: 3600)

Example:

aid-register \
  --auth https://auth.23blocks.com/acme \
  --token eyJhbGciOiJSUzI1NiJ9... \
  --role-id 2 \
  --description "Handles file processing"

aid-request — Request Registration (Agent-Initiated)

Submit a registration request without an admin token. The request is created in pending status and must be approved by an admin before the agent can get tokens.

aid-request --auth <url> [options]
FlagDescription
--auth, -aAuth server URL (required)
--api-key, -kAPI key (X-Api-Key header)
--name, -nDisplay name (default: agent name)
--description, -dWhy this agent needs access
--poll, -pCheck status of a pending request

Examples:

# Request registration
aid-request --auth https://auth.23blocks.com/acme \
  --description "Handles customer support ticket triage"

# Check if request has been approved
aid-request --auth https://auth.23blocks.com/acme --poll

What it does:

  1. Reads the agent's Ed25519 public key and identity
  2. POSTs to POST /agent_registrations/request (no auth token required)
  3. Server creates a pending registration and returns an authorization_url
  4. Displays the authorization_url for the admin to visit and approve
  5. Stores the registration ID locally for polling
  6. With --poll, checks the current status of the pending request

aid-token — Request a JWT Token

Authenticates using your Agent Identity and returns a scoped RS256 JWT.

aid-token --auth <url> [options]
FlagDescription
--auth, -aAuth server URL (required, unless --resource is given)
--resource, -rProtected resource URL — auth server is discovered via RFC 9728
--api-key, -kAPI key (X-Api-Key header)
--scope, -sSpace-separated scopes (default: all registered)
--credential-type, -caccess_token (JWT, default) or api_key (opaque)
--json, -jOutput as JSON
--quiet, -qOutput only the token (for piping)
--no-cacheSkip token cache, always request fresh

Examples:

# Get token (uses cache if valid)
aid-token --auth https://auth.23blocks.com/acme

# Get token with specific scopes
aid-token --auth https://auth.23blocks.com/acme --scope "files:read files:write"

# Get just the token string for scripts
TOKEN=$(aid-token -a https://auth.23blocks.com/acme -q)

aid-status — Show Identity & Auth Status

Displays your agent identity, auth server registrations, and cached tokens.

aid-status [options]
FlagDescription
--json, -jOutput as JSON

Token Caching

AID caches tokens locally at ~/.agent-messaging/agents/<name>/tokens/. Cached tokens are:

  • Automatically reused if still valid (with 60-second buffer)
  • Scope-aware — requesting different scopes gets a fresh token
  • Cleaned up when expired
  • Skippable with --no-cache

OAuth 2.0 Grant Type

AID uses a custom OAuth 2.0 grant type: urn:aid:agent-identity

Token request:

POST {token_endpoint}
Content-Type: application/x-www-form-urlencoded

grant_type=urn%3Aaid%3Aagent-identity
&agent_identity=<base64url-encoded-signed-agent-identity>
&proof=<base64url-encoded-proof-of-possession>
&scope=files%3Aread+files%3Awrite
&requested_credential_type=access_token

The token_endpoint is resolved from the registration file (returned by the auth server during registration/approval). Falls back to {auth_url}/oauth/token.

Credential format (requested_credential_type)

The optional requested_credential_type parameter tells the auth server which credential format to issue. Values:

ValueDescription
access_token (default)RS256 JWT. Target APIs validate offline via JWKS.
api_keyOpaque bearer token. Target APIs validate online via RFC 7662 introspection. Useful for services without JWKS infrastructure.

When omitted, the server returns access_token. The response always includes a credential_type field indicating what was issued. The value (JWT or opaque key) is in the standard access_token response field regardless of type.

Discovery: auth servers advertise which formats they support via aid_grant.credential_types_supported in the authorization-server metadata. Clients SHOULD reject configurations where the requested type isn't advertised.

Client usage:

# Default — get a JWT
aid-token --auth https://auth.example.com/tenant

# Get an opaque API key instead
aid-token --auth https://auth.example.com/tenant --credential-type api_key

Agent Identity (canonical JSON — sorted keys, compact, no whitespace — then base64url-encoded):

{"address":"agent-name@org.local","aid_version":"1.0","alias":"agent-name","expires_at":"2026-09-21T00:00:00Z","fingerprint":"abc123...","issued_at":"2026-03-21T00:00:00Z","key_algorithm":"Ed25519","public_key":"-----BEGIN PUBLIC KEY-----\\n...","signature":"<base64url-ed25519-signature>"}

The identity MUST be signed in canonical form (alphabetically sorted keys, compact JSON with no extra whitespace). The signature field is added AFTER signing — the signed payload excludes it.

Proof of Possession:

sign_input = "aid-token-exchange\n{unix_timestamp}\n{oidc_issuer}"
proof = base64url(ed25519_sign(sign_input) + timestamp_string)

The oidc_issuer is the auth server's OIDC issuer URL for the tenant (e.g., https://auth.example.com/apps/tenant-id), stored in the registration file after approval. Falls back to the --auth URL if not available.

The proof has a 5-minute validity window to prevent replay attacks.

Scope Resolution

When the scope parameter is included in the token request, the auth server MUST apply scope intersection:

  1. Let registered_scopes = the set of scopes available to the agent via its assigned role
  2. Let requested_scopes = the scopes in the scope parameter (space-separated)
  3. If requested_scopes is empty or omitted, grant all registered_scopes
  4. If any scope in requested_scopes is NOT in registered_scopes, the server MUST reject the request with invalid_scope
  5. Otherwise, grant exactly requested_scopes (which is a subset of registered_scopes)

This prevents scope escalation — an agent cannot request permissions beyond what its role allows. It also enables least-privilege token requests, where an agent requests only the scopes it needs for a specific task.

Error response for invalid scopes:

{
  "error": "invalid_scope",
  "error_description": "Requested scopes not permitted: admin:write, users:delete"
}

Discovery (RFC 9728 / RFC 8414)

AID is discoverable through standard OAuth metadata documents. An agent that knows only the URL of a protected resource can discover the auth server, the AID grant endpoints, and the supported scopes without any per-tenant configuration.

This mirrors the structure used by WorkOS auth.md (agent_auth block) and is intentionally side-by-side compatible: an auth server can advertise both AID's aid_grant and auth.md's agent_auth blocks on the same metadata document.

Step 1 — Protected Resource Metadata (RFC 9728)

The target API publishes RFC 9728 Protected Resource Metadata at /.well-known/oauth-protected-resource. This is standard OAuth — no AID-specific fields.

GET https://api.acme.com/.well-known/oauth-protected-resource
{
  "resource": "https://api.acme.com/",
  "resource_name": "Acme API",
  "authorization_servers": ["https://auth.acme.com/zoom"],
  "scopes_supported": ["files:read", "files:write", "tickets:read"],
  "bearer_methods_supported": ["header"]
}

Target APIs MAY also return this URL via a WWW-Authenticate: Bearer resource_metadata="…" header on 401 responses, so agents discover the metadata document on first denied request.

Step 2 — Authorization Server Metadata (RFC 8414 + aid_grant)

The auth server publishes RFC 8414 Authorization Server Metadata at /.well-known/oauth-authorization-server. It MUST advertise urn:aid:agent-identity in grant_types_supported and SHOULD include an aid_grant block listing the AID-specific endpoints:

GET https://auth.acme.com/zoom/.well-known/oauth-authorization-server
{
  "issuer": "https://auth.acme.com/zoom",
  "token_endpoint": "https://auth.acme.com/zoom/oauth/token",
  "introspection_endpoint": "https://auth.acme.com/zoom/oauth/introspect",
  "jwks_uri": "https://auth.acme.com/zoom/.well-known/jwks.json",
  "grant_types_supported": [
    "urn:aid:agent-identity",
    "client_credentials"
  ],
  "scopes_supported": ["files:read", "files:write", "tickets:read"],
  "aid_grant": {
    "aid_version": "1.0",
    "registration_endpoint": "https://auth.acme.com/zoom/agent_registrations",
    "registration_request_endpoint": "https://auth.acme.com/zoom/agent_registrations/request",
    "code_resolution_endpoint": "https://auth.acme.com/zoom/agent_registrations/resolve",
    "agent_authorization_uri": "https://app.acme.com/agents/authorize",
    "key_algorithms_supported": ["Ed25519"],
    "credential_types_supported": ["access_token", "api_key"],
    "polling_interval": 5
  }
}

aid_grant block fields

FieldRequiredDescription
aid_versionMUSTProtocol version this server implements (currently "1.0")
registration_endpointMUSTURL for admin-initiated registration (POST with admin JWT)
registration_request_endpointMUSTURL for agent-initiated registration (no admin token; creates pending)
code_resolution_endpointSHOULDURL the admin UI uses to resolve an authorization code into a registration
agent_authorization_uriSHOULDBase URL for the admin approval page (the verification_uri from RFC 8628)
key_algorithms_supportedMUSTArray of Ed25519 (and future algorithms); clients MUST reject if their key algorithm is absent
credential_types_supportedMAYArray of credential formats issued. Values: access_token (RS256 JWT, default) and/or api_key (opaque bearer). Clients SHOULD reject configurations where their requested type isn't listed.
polling_intervalMAYDefault interval returned in agent-initiated registration responses; clients SHOULD treat per-registration interval as authoritative

Step 3 — Use the discovered endpoints

Once the agent has resolved the aid_grant block, it has everything needed for the standard AID flow:

  • registration_endpoint / registration_request_endpoint for aid-register / aid-request
  • token_endpoint for aid-token
  • introspection_endpoint for target APIs that want real-time validation
  • scopes_supported to know what --scope values are valid

A client that supports discovery (aid-discover or --resource on token/request commands) replaces per-tenant --auth configuration with a single resource URL.

Token Introspection (RFC 7662)

Target APIs can verify agent tokens in real-time using the introspection endpoint. This is especially useful for checking if an agent has been suspended since the token was issued.

Request:

POST /:tenant/oauth/introspect
Content-Type: application/x-www-form-urlencoded

token=eyJhbGciOiJSUz...

Response (active agent):

{
  "active": true,
  "sub": "agent:abc123",
  "scope": "tickets:read tickets:write",
  "token_type": "Bearer",
  "agent_id": "abc123-uuid",
  "agent_address": "support-bot@tenant.local",
  "agent_name": "support-bot",
  "agent_role": "support",
  "agent_status": "active",
  "exp": 1711411200,
  "iat": 1711407600
}

Required Introspection Fields

When introspecting an agent token, the auth server MUST include these agent-specific fields alongside the standard RFC 7662 fields:

FieldTypeRequiredDescription
activebooleanMUSTWhether the token is valid AND the agent is active
substringMUSTAgent's subject identifier
scopestringMUSTSpace-separated granted scopes
token_typestringMUSTAlways "Bearer"
agent_idstringMUSTAgent's unique identifier (from registration)
agent_addressstringMUSTAgent's address (e.g., name@org.provider)
agent_namestringMUSTAgent's human-readable display name
agent_rolestringMUSTName of the role assigned to this agent
agent_statusstringMUSTCurrent lifecycle status: pending, active, suspended, or deleted
expintegerMUSTToken expiration (Unix timestamp)
iatintegerMUSTToken issued-at (Unix timestamp)
issstringSHOULDIssuer URL
jtistringSHOULDUnique token identifier

These fields allow target APIs to make authorization decisions based on agent identity, not just token validity. For example, an API can log which agent made each request, or apply per-agent rate limits.

Response (suspended agent):

{
  "active": false,
  "reason": "agent_suspended"
}

When active is false, the reason field SHOULD indicate why: agent_suspended, agent_not_found, token_expired, or invalid_token.

Target APIs can choose between:

  • Offline validation — verify the JWT signature via JWKS (fast, but can't detect suspension until token expires)
  • Online introspection — call the introspection endpoint (slower, but real-time status)

Agent Lifecycle

                            ┌───────────────────────────────────────┐
                            │                                       v
aid-request ──> pending ──> active ──> suspended ──> active   (reactivated)
                   │                           └──> deleted   (terminal)
                   └──> rejected                              (terminal)
StatusCan get tokens?Introspection returns
pendingNoactive: false, reason: registration_pending
activeYesactive: true
suspendedNo (403)active: false, reason: agent_suspended
rejectedNoactive: false, reason: agent_not_found
deletedNoactive: false, reason: agent_not_found

Admins control agent lifecycle via the registration API:

  • POST /agent_registrations/:unique_id/approve — approve a pending request and assign a role
  • POST /agent_registrations/:unique_id/reject — reject a pending request
  • POST /agent_registrations/:unique_id/suspend — immediately block token issuance and invalidate via introspection
  • POST /agent_registrations/:unique_id/reactivate — restore agent access

Error Handling

ErrorMeaningFix
agent_not_registeredAgent not registered with this serverRun aid-request or aid-register
registration_pendingRegistration awaiting admin approvalRun aid-request --poll to check status
invalid_grantAgent Identity signature invalidCheck agent keys match registration
invalid_proofProof of possession failedCheck system clock sync
invalid_scopeRequested scopes exceed permissionsTry without --scope
agent_suspendedAgent has been suspended by adminContact admin for reactivation

Polling Error Responses (RFC 8628)

When an agent polls for registration status, the auth server MUST return one of the following error codes aligned with RFC 8628 Section 3.5:

ErrorHTTP StatusMeaningAgent Action
authorization_pending200Registration not yet approvedKeep polling at the specified interval
slow_down429Agent is polling too frequentlyIncrease polling interval by 5 seconds
expired_token410Authorization code has expiredSubmit a new registration request with aid-request
access_denied403Admin rejected the registration requestDo not retry; contact admin or submit a new request

Example error response:

{
  "error": "slow_down",
  "error_description": "Polling too frequently. Increase interval to 10 seconds."
}

Security

  • No shared secrets — authentication uses Ed25519 asymmetric cryptography
  • No API keys to rotate — identity is the key pair itself
  • Human controls access — agents can request registration, but only an admin can approve and assign roles
  • Replay protection — proof of possession has a 5-minute window
  • Scoped tokens — JWTs contain only the scopes the agent's role allows
  • Local key storage — private keys never leave the agent's machine
  • Token cache security — cached tokens stored with 600 permissions

Compliance — OWASP Top 10 for Agentic Applications (2026)

AID is an identity-layer protocol. It directly addresses the OWASP risks that come from agents acting without scoped, attributable identity, and partially addresses risks where identity is a contributing control. Full details, including honest gaps and what to compose AID with for full coverage, are in docs/owasp-agentic-mapping.md.

CodeRiskAID coverage
ASI01Agent Goal HijackingPartial (attribution + revocation)
ASI02Tool Misuse and ExploitationFull (role-scoped JWTs + introspection)
ASI03Identity and Privilege AbuseFull (Ed25519 per-agent identity + PoP)
ASI04Agentic Supply Chain VulnerabilitiesPartial (signed discovery + JWKS)
ASI05Unexpected Code ExecutionOut of scope (defer to sandboxing)
ASI06Memory and Context PoisoningPartial (tenant isolation via identity)
ASI07Insecure Inter-Agent CommunicationPartial → Full with AMP
ASI08Cascading FailuresPartial (sub-second kill switch via introspection)
ASI09Human-Agent Trust ExploitationPartial (source attribution via introspection)
ASI10Rogue AgentsPartial (audit trail + revocation)

Score: 2 Full · 7 Partial · 1 Out of scope. AID is the identity layer of a complete agentic-AI security posture — compose with a runtime governance tool (Microsoft Agent Governance Toolkit), an inter-agent messaging protocol (AMP), and a code-execution sandbox for full coverage.

For Auth Server Implementers

To support AID, your OAuth 2.0 server needs:

  1. Agent Registration endpointPOST /agent_registrations (admin-initiated) and POST /agent_registrations/request (agent-initiated, creates pending registration)
  2. Registration approvalPOST /agent_registrations/:unique_id/approve with role assignment, POST /agent_registrations/:unique_id/reject
  3. Authorization URL — return authorization_url with a temporary opaque code in agent-initiated registration responses, and a GET /agent_registrations/resolve?code={code} endpoint for the admin UI to resolve it
  4. Token endpointPOST /{tenant}/oauth/token supporting grant_type=urn:aid:agent-identity
  5. Ed25519 verification — validate Agent Identity signatures (canonical JSON) and proof of possession
  6. JWKS endpointGET /.well-known/jwks.json so target APIs can validate issued JWTs
  7. Discovery metadata — publish /.well-known/oauth-authorization-server (RFC 8414) advertising urn:aid:agent-identity in grant_types_supported and an aid_grant block with the endpoints above. Resource servers SHOULD also publish /.well-known/oauth-protected-resource (RFC 9728) so agents can discover the auth server from the API URL.
  8. Introspection endpointPOST /oauth/introspect (RFC 7662) for real-time token validation
  9. Lifecycle management — suspend/reactivate endpoints for admin control
  10. Registration response metadata — return token_endpoint and oidc_issuer in registration/approval responses so agents can resolve these without guessing tenant paths

Target APIs (the services your agents call) can:

  • Minimal: Validate RS256 JWTs using the auth server's JWKS endpoint (no AID-specific code)
  • Full: Also call the introspection endpoint for real-time suspension checking
  • Discoverable: Publish RFC 9728 Protected Resource Metadata so agents can find the auth server from the API URL alone

Standards referenced by AID

StandardRole in AID
RFC 6749OAuth 2.0 framework — AID is a custom grant type
RFC 7515 / 7519JWS / JWT — issued access tokens are RS256 JWTs
RFC 7662Token Introspection — real-time validation and revocation
RFC 8414Authorization Server Metadata — hosts the aid_grant block
RFC 8628Device Authorization Grant — model for user_code, expires_in, interval, and polling error semantics
RFC 8693Token Exchange — referenced for future delegation chains
RFC 9728Protected Resource Metadata — resource-server discovery

See the 23blocks Authentication API for a reference implementation.

Interoperability with AMP

AID and AMP (Agent Messaging Protocol) are independent protocols from the same organization. If both are installed, they share the ~/.agent-messaging/agents/ directory — one identity serves both protocols. Neither requires the other.

AIDAMP
PurposeAuthentication & authorizationMessaging between agents
What it doesGets JWT tokens for API accessSends/receives messages
Requires the other?NoNo
SharedEd25519 identity, key storageEd25519 identity, key storage

License

MIT