Agent Auth (AAuth) Protocol
AAuth is an authorization framework for autonomous agents acting on behalf of users and organizations. It gives agents and resources cryptographic identities, binds every token to a key (proof-of-possession), and signs every request with RFC 9421 — HTTP Message Signatures: cryptographic signatures over specific HTTP request components (method, path, selected headers, body digest). The verifier rebuilds the signature input from the request and checks the signature — so every call has a replay-proof proof of possession.
AAuth is an emerging protocol under active development. LumoAuth tracks the current draft and may adjust as the standard evolves.
Overview
AAuth extends OAuth 2.1 for agents with cryptographic identities. Unlike traditional OAuth clients that require user interaction for each authorization, AAuth supports:
- Autonomous operation — agents act independently once authorized
- Cryptographic identity — all parties have verifiable identities via public-key cryptography
- Proof-of-possession — every token is bound to a key, preventing token theft
- HTTP Message Signing (RFC 9421) — every request is signed, protecting integrity and replay
- Progressive authentication — from "no user" to "user actively present"
- Delegation — agents delegate capabilities to sub-agents
- Token exchange — agents exchange tokens across trust boundaries
Key concepts
Parties in AAuth
| Party | Description | Identity |
|---|---|---|
| Agent | An autonomous software entity | HTTPS URL with cryptographic identity |
| Resource | A protected service or API | URI with cryptographic identity |
| Authorization Server | Issues tokens after verifying identities | LumoAuth organization endpoint |
| User | Human who authorizes agents | Optional, depending on auth level |
Token types
AAuth defines three JWT token types. RFC 7519 — JWT is a signed JSON payload; all AAuth tokens are JWTs bound to a public key:
- Resource Token (
resource+jwt) — proves the resource's identity to the authorization server. Issued to resources, short-lived (5 minutes), bound to the resource's public key viacnf.jkt. - Auth Token (
auth+jwt) — authorizes an agent's access to a specific resource. Issued to the agent for a specific agent+resource+user combination, 1 hour default, bound to the agent's public key viacnf.
Authentication levels
| Level | Description | User interaction | Use cases |
|---|---|---|---|
none | No user identity | None | Public APIs, anonymous access |
authenticated | User logged in once | Initial only | General agent tasks |
authorized | User explicitly approved | Explicit consent | Sensitive operations |
user | User actively present | Per-request | High-security operations |
Protocol flows
Flow 1: Agent registration
Before participating in AAuth, agents and resources must register with the authorization server.
You can register agents and resources through the Organization Portal at
/orgs/{orgId}/portal/aauth/agents, or via the API.
Flow 2: Direct authorization (no user)
For none or authenticated level access, no user interaction is needed:
POST https://app.lumoauth.dev/orgs/{orgId}/api/v1/aauth/agent/token
Content-Type: application/json
Agent-Auth: [HTTP Message Signature]
{
"request_type": "auth",
"agent_token": "eyJhbGc...",
"resource_token": "eyJhbGc...",
"scope": "read write"
}
Response 200:
{
"access_token": "eyJhbGc...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "read write",
"refresh_token": "aauth_refresh_abc123def456"
}
Flow 3: User authorization (explicit consent)
For authorized or user level access:
Worked example: AI email agent
A complete walkthrough of an AI email agent reading and sending mail for a user.
Scenario
| Component | Identifier | Description |
|---|---|---|
| Agent | https://email-agent.aiservices.com | AI email assistant |
| Resource | https://api.mailservice.com | Email API service |
| Auth Server | https://app.lumoauth.dev/orgs/acme-corp | LumoAuth organization |
| User | alice@acme-corp.com | Alice wants to use the agent |
Step 1: Register the agent (one-time)
In the Organization Portal, go to /orgs/acme-corp/portal/aauth/agents/create and configure:
- Name: AI Email Assistant
- Identifier:
https://email-agent.aiservices.com - JWKS URI:
https://email-agent.aiservices.com/.well-known/jwks.json - Redirect URIs:
https://email-agent.aiservices.com/oauth/callback - Allowed Scopes:
email:read email:send email:delete - Signing Algorithms:
ed25519,rsa-pss-sha512 - Features: ✅ User Authorization, ✅ Delegation, ✅ Token Exchange
Step 2: Register the resource (one-time)
At /orgs/acme-corp/portal/aauth/resources/create:
- Name: Corporate Email API
- Identifier:
https://api.mailservice.com - JWKS URI:
https://api.mailservice.com/.well-known/jwks.json - Supported Scopes:
email:read,email:send,email:delete - Default Auth Level:
authorized(requires explicit user consent)
Step 3: Agent generates a key pair
AAuth uses Ed25519 by default. Ed25519 is a modern elliptic-curve signature scheme — short keys, fast, widely supported. The agent generates a key pair per user session:
// Email agent code (Node.js)
const crypto = require('crypto');
// Generate Ed25519 key pair for this user session
const { publicKey, privateKey } = crypto.generateKeyPairSync('ed25519', {
publicKeyEncoding: { type: 'spki', format: 'pem' },
privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
});
// Convert to JWK format
const publicKeyJwk = {
kty: 'OKP',
crv: 'Ed25519',
x: publicKey.export({ type: 'spki', format: 'der' })
.slice(-32).toString('base64url')
};
// Store private key securely for this session
session.agentPrivateKey = privateKey;
session.agentPublicKeyJwk = publicKeyJwk;
Step 4: Obtain an agent token
The main agent server issues an agent token to Alice's session delegate:
const agentToken = await fetch('https://email-agent.aiservices.com/delegate/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
delegate_sub: `https://email-agent.aiservices.com/session/${alice.sessionId}`,
delegate_public_key: publicKeyJwk,
audience: ['https://app.lumoauth.dev/orgs/acme-corp/api/v1'],
lifetime: 3600
})
}).then(r => r.json());
console.log('Agent Token:', agentToken.access_token);
Resulting agent token claims:
{
"typ": "agent+jwt",
"alg": "EdDSA",
"kid": "agent-key-1"
}
{
"iss": "https://email-agent.aiservices.com",
"sub": "https://email-agent.aiservices.com/session/alice-123",
"aud": ["https://app.lumoauth.dev/orgs/acme-corp/api/v1"],
"exp": 1706817600,
"iat": 1706814000,
"jti": "agent_token_abc123",
"cnf": {
"jkt": "SHA256_thumbprint_of_delegate_public_key"
}
}
Step 5: Resource creates a resource token
The email API creates a short-lived resource token to prove its identity:
const resourceToken = await fetch(
'https://app.lumoauth.dev/orgs/acme-corp/api/v1/aauth/resource/token',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer https://api.mailservice.com`
},
body: JSON.stringify({
resource_identifier: 'https://api.mailservice.com',
audience: 'https://app.lumoauth.dev/orgs/acme-corp/api/v1',
lifetime: 300
})
}
).then(r => r.json());
console.log('Resource Token:', resourceToken.resource_token);
Step 6: Request authorization with user consent
The agent requests authorization with Alice's consent. This requires an RFC 9421 HTTP Message Signature:
const crypto = require('crypto');
// Build request body
const requestBody = {
request_type: 'auth',
agent_token: agentToken.access_token,
resource_token: resourceToken.resource_token,
scope: 'email:read email:send',
redirect_uri: 'https://email-agent.aiservices.com/oauth/callback'
};
// Create HTTP Message Signature
const method = 'POST';
const targetUri = 'https://app.lumoauth.dev/orgs/acme-corp/api/v1/aauth/agent/token';
const contentType = 'application/json';
const bodyBytes = Buffer.from(JSON.stringify(requestBody));
const contentDigest = `sha-256=:${crypto.createHash('sha256').update(bodyBytes).digest('base64')}:`;
const signatureBase = [
`"@method": ${method}`,
`"@target-uri": ${targetUri}`,
`"content-type": ${contentType}`,
`"content-digest": ${contentDigest}`
].join('\n');
const signature = crypto.sign(null, Buffer.from(signatureBase), session.agentPrivateKey);
const signatureB64 = signature.toString('base64');
const agentAuthHeader = `sig1=:${signatureB64}:; label="sig1"; alg="ed25519"; ` +
`keyid="https://email-agent.aiservices.com#key-1"; ` +
`created=${Math.floor(Date.now() / 1000)}; ` +
`covered="@method @target-uri content-type content-digest"`;
// Request authorization
const authResponse = await fetch(targetUri, {
method: 'POST',
headers: {
'Content-Type': contentType,
'Content-Digest': contentDigest,
'Agent-Auth': agentAuthHeader
},
body: JSON.stringify(requestBody)
});
if (authResponse.status === 401) {
const authData = await authResponse.json();
console.log('User authorization required');
console.log('Authorization URL:', authData.auth_url);
// Redirect Alice to authorization URL
redirectUser(authData.auth_url);
}
Step 7: User sees the consent screen
Alice is redirected to the LumoAuth consent screen, which shows:
- Agent: AI Email Assistant (
https://email-agent.aiservices.com) - Will access: Corporate Email API (
https://api.mailservice.com) - Permissions requested: ☑️
email:read— read emails and metadata; ☑️email:send— send emails on your behalf - Trust indicators: ✅ Verified agent identity ✅ Verified resource identity ✅ Cryptographically secured
This application will act on behalf of Alice (alice@acme-corp.com).
Step 8: Exchange the authorization code for tokens
After Alice approves, she's redirected back with an authorization code:
// Alice is redirected back with authorization code
// https://email-agent.aiservices.com/oauth/callback?code=authz_code_abc123def456
const codeExchangeBody = {
request_type: 'code',
code: 'authz_code_abc123def456',
request_token: 'aauth_req_abc123'
};
// Sign and exchange code for tokens
const tokenResponse = await fetch(targetUri, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Agent-Auth': signRequest(codeExchangeBody) // Using same signing logic
},
body: JSON.stringify(codeExchangeBody)
}).then(r => r.json());
console.log('Auth Token:', tokenResponse.access_token);
console.log('Refresh Token:', tokenResponse.refresh_token);
console.log('Expires in:', tokenResponse.expires_in, 'seconds');
// Store tokens securely
session.authToken = tokenResponse.access_token;
session.refreshToken = tokenResponse.refresh_token;
Step 9: Access the email API
// Read emails
const signEmailRequest = (method, url, authToken) => {
const signatureBase = [
`"@method": ${method}`,
`"@target-uri": ${url}`,
`"authorization": Bearer ${authToken}`
].join('\n');
const signature = crypto.sign(null, Buffer.from(signatureBase), session.agentPrivateKey);
return `sig1=:${signature.toString('base64')}:; label="sig1"; alg="ed25519"; ` +
`keyid="https://email-agent.aiservices.com#key-1"; ` +
`created=${Math.floor(Date.now() / 1000)}; ` +
`covered="@method @target-uri authorization"`;
};
const emails = await fetch('https://api.mailservice.com/v1/emails/inbox', {
method: 'GET',
headers: {
'Authorization': `Bearer ${session.authToken}`,
'Agent-Auth': signEmailRequest('GET',
'https://api.mailservice.com/v1/emails/inbox',
session.authToken)
}
}).then(r => r.json());
console.log('Fetched', emails.length, 'emails');
// Send email
const emailData = {
to: 'bob@example.com',
subject: 'Meeting Summary',
body: 'Here is the summary of our meeting...',
from: 'alice@acme-corp.com'
};
const emailBody = JSON.stringify(emailData);
const emailDigest = `sha-256=:${crypto.createHash('sha256')
.update(Buffer.from(emailBody))
.digest('base64')}:`;
const sendResult = await fetch('https://api.mailservice.com/v1/emails/send', {
method: 'POST',
headers: {
'Authorization': `Bearer ${session.authToken}`,
'Content-Type': 'application/json',
'Content-Digest': emailDigest,
'Agent-Auth': signEmailRequestWithBody('POST',
'https://api.mailservice.com/v1/emails/send',
session.authToken, emailDigest)
},
body: emailBody
}).then(r => r.json());
console.log('Email sent:', sendResult.message_id);
Step 10: Refresh the token
const refreshBody = {
request_type: 'refresh',
refresh_token: session.refreshToken,
scope: 'email:read email:send'
};
const refreshResponse = await fetch(targetUri, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Agent-Auth': signRequest(refreshBody)
},
body: JSON.stringify(refreshBody)
}).then(r => r.json());
// Update tokens
session.authToken = refreshResponse.access_token;
session.refreshToken = refreshResponse.refresh_token;
console.log('Tokens refreshed');
Security features
1. Proof-of-possession binding
All tokens are bound to cryptographic keys. A stolen token is useless without the matching private key.
2. HTTP Message Signing (RFC 9421)
Every request is signed, protecting against:
- Request tampering
- Replay attacks (with nonce validation)
- Man-in-the-middle attacks
3. Cryptographic identity verification
All parties have verifiable cryptographic identities with published JWKS endpoints.
4. Delegation chain tracking
The act (actor) claim tracks the complete delegation chain:
{
"sub": "user_12345",
"client_id": "https://primary-agent.com",
"act": {
"sub": "https://delegate-agent.com",
"act": {
"sub": "https://sub-delegate.com"
}
}
}
API reference
Agent token endpoint
POST /orgs/{orgId}/api/v1/aauth/agent/token
Main endpoint for token operations. Requires an RFC 9421 HTTP Message Signature via the Agent-Auth header.
| request_type | Purpose | Required fields |
|---|---|---|
auth | Request new auth token | agent_token, resource_token, scope |
code | Exchange authorization code | code, request_token |
refresh | Refresh auth token | refresh_token |
exchange | Exchange token across boundaries | subject_token, resource_token |
Discovery endpoints
GET /.well-known/aauth-issuer — authorization server metadata (supported algorithms, token types, endpoints).
GET /.well-known/aauth-agent — agent metadata including JWKS URI and supported capabilities (hosted by the agent).
GET /.well-known/aauth-resource — resource metadata including supported scopes and auth levels (hosted by the resource).
Best practices
For agents
- Generate a unique key pair per user session — don't reuse keys across users
- Store private keys securely — use an HSM if possible, encrypt at rest
- Request only the scopes you need
- Refresh proactively — before tokens expire
- Validate all responses — check signatures and claims
For resources
- Verify proof-of-possession — don't accept tokens without valid signatures
- Validate token claims — check
aud,scope,exp - Implement clear error responses
- Cache validated tokens — to rate-limit verification cost
- Log security events — failed verifications, insufficient scopes
Never store private keys in source code or environment variables in production. Use a Hardware Security Module (HSM) or secure key management service.
Related
- AAuth Quickstart — minimal working setup
- Agent Registry — manage agent registrations in your organization
- Chain of Agency — token exchange and delegation patterns