Skip to main content

Agent Auth (AAuth) Protocol

A modern authorization framework designed specifically for autonomous agents operating on behalf of users and organizations, featuring cryptographic identity, proof-of-possession, and HTTP message signing.

Specification Status

AAuth is an emerging protocol under active development. The implementation in LumoAuth follows the current draft specification and may evolve as the standard matures.

Overview

Agent Auth (AAuth) is an authorization protocol that extends OAuth 2.1 to support autonomous agents with cryptographic identities. Unlike traditional OAuth clients that require user interaction for each authorization, AAuth enables:

  • Autonomous Operation: Agents can act independently once authorized
  • Cryptographic Identity: All parties have provable identities using public key cryptography
  • Proof-of-Possession: All tokens are bound to cryptographic keys, preventing token theft
  • HTTP Message Signing: All requests are signed using RFC 9421, ensuring request integrity
  • Progressive Authentication: Support for authentication levels from no user to fully authenticated user
  • Delegation: Agents can delegate capabilities to sub-agents
  • Token Exchange: Agents can exchange tokens across trust boundaries

Key Concepts

Parties in AAuth

PartyDescriptionIdentity
AgentAn autonomous software entityHTTPS URL with cryptographic identity
ResourceA protected service or APIURI with cryptographic identity
Authorization ServerIssues tokens after verifying identitiesLumoAuth tenant endpoint
UserHuman who authorizes agentsOptional, depending on auth level

Token Types

AAuth introduces three specialized JWT token types, all proof-of-possession bound:

Resource Token (resource+jwt)

Purpose: Proves resource's identity to authorization server

Issued to: Resources

Lifetime: 5 minutes (short-lived)

Bound to: Resource's public key via cnf.jkt

Auth Token (auth+jwt)

Purpose: Authorizes agent access to a specific resource

Issued to: Agent for specific agent+resource+user combination

Lifetime: 1 hour (default)

Bound to: Agent's public key via cnf claim

Authentication Levels

AAuth supports four authentication levels for different use cases:

LevelDescriptionUser InteractionUse Cases
noneNo user identityNonePublic APIs, anonymous access
authenticatedUser logged in onceInitial onlyGeneral agent tasks
authorizedUser explicitly approvedExplicit consentSensitive operations
userUser actively presentPer-requestHigh-security operations

Protocol Flows

Flow 1: Agent Registration

Before participating in AAuth, agents and resources must register with the authorization server.

Portal UI

You can register agents and resources through the LumoAuth Tenant Portal UI at /t/{tenantSlug}/portal/aauth/agents or via the API.

Flow 2: Direct Authorization (No User)

For none or authenticated level access when no user interaction is needed:

POST https://app.lumoauth.dev/t/{tenantSlug}/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"
}

For authorized or user level access requiring user consent:

Comprehensive Example: AI Email Agent

Let's walk through a complete example of an AI email agent that reads and sends emails on behalf of a user.

Scenario Overview

ComponentIdentifierDescription
Agenthttps://email-agent.aiservices.comAI-powered email assistant
Resourcehttps://api.mailservice.comEmail API service
Auth Serverhttps://app.lumoauth.dev/t/acme-corpLumoAuth tenant
Useralice@acme-corp.comAlice wants to use the agent

Step 1: Register Agent (One-time Setup)

Administrator registers the email agent in LumoAuth portal. Navigate to /t/acme-corp/portal/aauth/agents/create and configure:

  • Agent Name: "AI Email Assistant"
  • Agent 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 Resource (One-time Setup)

Administrator registers the email API. Navigate to /t/acme-corp/portal/aauth/resources/create and configure:

  • Resource Name: "Corporate Email API"
  • Resource 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 Key Pair

Alice wants to use the AI email assistant. The agent generates a unique key pair for her session:

// Email agent code (JavaScript/Node.js example)
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 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/t/acme-corp/api/v1'],
lifetime: 3600
})
}).then(r => r.json());

console.log('Agent Token:', agentToken.access_token);

The resulting agent token has the following 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/t/acme-corp/api/v1"],
"exp": 1706817600,
"iat": 1706814000,
"jti": "agent_token_abc123",
"cnf": {
"jkt": "SHA256_thumbprint_of_delegate_public_key"
}
}

Step 5: Resource Creates Resource Token

The email API creates a short-lived resource token to prove its identity:

const resourceToken = await fetch(
'https://app.lumoauth.dev/t/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/t/acme-corp/api/v1',
lifetime: 300
})
}
).then(r => r.json());

console.log('Resource Token:', resourceToken.resource_token);

The agent requests authorization with Alice's consent. This requires creating an 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/t/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);
}

Alice is redirected to the LumoAuth consent screen:

AI Email Assistant wants to access your emails

Agent: AI Email Assistant

🔗 https://email-agent.aiservices.com

Will be accessing: Corporate Email API

🔗 https://api.mailservice.com

Requested permissions:

☑️ email:read - Read your 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 Authorization Code for Tokens

After Alice approves, she's redirected back with an authorization code. The agent exchanges it for tokens:

// 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 Email API

Now the agent can read and send emails on Alice's behalf:

// 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: Token Refresh

When the auth token expires (after 1 hour), use the refresh token to obtain a new one:

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) // Using same signing logic
},
body: JSON.stringify(refreshBody)
}).then(r => r.json());

// Update tokens
session.authToken = refreshResponse.access_token;
session.refreshToken = refreshResponse.refresh_token;

console.log('Tokens refreshed successfully');

Security Features

1. Proof-of-Possession Binding

All tokens are bound to cryptographic keys. Token theft is mitigated because stolen tokens cannot be used without the corresponding private key.

2. HTTP Message Signing (RFC 9421)

All requests are signed, preventing:

  • 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 /t/\{tenantSlug\}/api/v1/aauth/agent/token

Main endpoint for token operations. Requires HTTP Message Signature via Agent-Auth header.

request_typePurposeRequired Fields
authRequest new auth tokenagent_token, resource_token, scope
codeExchange authorization codecode, request_token
refreshRefresh auth tokenrefresh_token
exchangeExchange token across boundariessubject_token, resource_token

Discovery Endpoints

GET /.well-known/aauth-issuer

Returns authorization server metadata including supported algorithms, token types, and endpoints.

GET /.well-known/aauth-agent

Returns agent metadata including JWKS URI and supported capabilities (hosted by the agent).

GET /.well-known/aauth-resource

Returns resource metadata including supported scopes and auth levels (hosted by the resource).

Best Practices

For Agents

  • Generate unique key pairs per user session - Don't reuse keys across users
  • Store private keys securely - Use HSM when possible, encrypt at rest
  • Request minimum necessary scopes - Only request what you need
  • Handle token refresh gracefully - Refresh proactively before expiration
  • Validate all responses - Check signatures and claims

For Resources

  • Always verify proof-of-possession - Don't accept tokens without valid signatures
  • Validate token claims - Check aud, scope, and exp
  • Implement proper error responses - Return helpful error descriptions
  • Rate limit token verification - Cache validated tokens
  • Log security events - Track failed verifications and insufficient scopes
Security Warning

Never store private keys in source code or environment variables in production. Use a Hardware Security Module (HSM) or secure key management service.

Get started with Agent Auth in 5 minutes

[Agent Registry

Manage agent registrations in your tenant](/agents/registry)

[Chain of Agency

Token exchange and delegation patterns](/agents/delegation)