Skip to main content

Enable Security for AI Agents

Give your AI agents verified identities, scoped capabilities, and auditable authorization. This guide covers registering an agent, issuing signed credentials, authorizing calls with the Ask API, and optionally requesting just-in-time permissions for sensitive actions.

Prerequisites
  • A LumoAuth organization — sign up at app.lumoauth.dev; agent identity (AAuth) is available on every plan, nothing to switch on
  • Node.js 18+ or Python 3.9+

Step 1: Register Your Agent​

Every agent needs a registered identity. In the Organization Portal, go to AI Agents → Agents and click Register agent.

The form is a three-step wizard.

StepWhat to enter
Who is this agentAgent name (e.g. summarizer-agent), an optional description, the owner and the framework
How it proves its identityThe credential type. Pick AAuth to register a public key: JWKS URL (your agent's public key endpoint), or paste the key set into JWKS (inline). Or pick Client ID and secret for the client-credentials examples in Step 3
What it may doCapabilities (the permissions that this agent may request, picked from a list grouped by resource), plus optional acting-for-people settings and a budget

Step 1 of the Register agent wizard with the agent name and description filled in

If you chose Client ID and secret, the next page shows the client ID and secret once. Copy them before leaving the page.

The Agent created page showing the client ID and the one-time client secret

Or register via API (the caller needs the agents.create permission; the response is { "success": true, "agent_id": "...", "name": "...", "workload_identity": null }):

curl -X POST https://app.lumoauth.dev/orgs/YOUR_ORG_ID/api/v1/agents/register \
-H "Authorization: Bearer ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "summarizer-agent",
"capabilities": ["document.read", "document.summarize"]
}'

An AAuth public key is registered in the wizard above (JWKS URL or JWKS (inline)), or on the agent's page afterwards.


Step 2: Generate Agent Credentials​

Agents can authenticate with signed JWTs (signed tokens) backed by a key pair. Ed25519, a modern public-key signature algorithm, is recommended.

Generate a key pair​

// Node.js
const crypto = require('crypto');

const { publicKey, privateKey } = crypto.generateKeyPairSync('ed25519', {
publicKeyEncoding: { type: 'spki', format: 'pem' },
privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
});
console.log('Public key:\n', publicKey);
console.log('Private key (store securely):\n', privateKey);
# Python
from cryptography.hazmat.primitives.asymmetric import ed25519
from cryptography.hazmat.primitives import serialization

private_key = ed25519.Ed25519PrivateKey.generate()
public_key = private_key.public_key()

pub_pem = public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo,
)
print(pub_pem.decode())

Provide the public key when registering the agent (as a JWKS, the list of public keys used to verify the agent's signatures). Keep the private key in your secret manager. The SDKs generate both for you: AAuthClient.generate_keypair() in Python, generateKeypair() from @lumoauth/agent in Node.


Step 3: Authenticate Your Agent (Get a Token)​

Agents authenticate with the OAuth 2.0 client-credentials grant and receive a scoped access token. Use the lumoauth Python SDK, or call the token endpoint directly from any language:

# Python — using the `lumoauth` SDK
from lumoauth import LumoAuthAgent

# Reads LUMOAUTH_URL, LUMOAUTH_ORG_ID, AGENT_CLIENT_ID, AGENT_CLIENT_SECRET
agent = LumoAuthAgent()
agent.authenticate(scopes=["document.read", "document.summarize"])

print(agent.access_token) # scoped bearer token
# Any language — client-credentials token request
curl -X POST https://app.lumoauth.dev/orgs/YOUR_ORG_ID/api/v1/oauth/token \
-d grant_type=client_credentials \
-d client_id="$AGENT_CLIENT_ID" \
-d client_secret="$AGENT_CLIENT_SECRET" \
-d 'scope=document.read document.summarize'
Two credential models

The client-credentials model above (client id + secret) is the quickest path. For cryptographic agent identity with signed HTTP requests (every request carries a signature made with the Ed25519 key from Step 2), use the AAuth protocol instead.


Step 4: Authorize Agent Actions​

Use the Ask API for a pre-flight permission check — ideal inside the reasoning loop of an LLM. It takes an action slug plus optional context (not a free-text question):

curl -X POST https://app.lumoauth.dev/orgs/YOUR_ORG_ID/api/v1/agents/ask \
-H "Authorization: Bearer AGENT_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"action": "document.summarize", "context": {"id": "doc_99"}}'
{
"allowed": true,
"action": "document.summarize",
"context": { "id": "doc_99" },
"reason": "Agent has authorized capability for 'document.summarize'.",
"audit_id": "8f3a1b2c4d5e6f7a"
}

See the Ask API reference for the full request/response contract.

Or use a traditional permission check. The authorization endpoints are global (/api/v1/authz/..., no /orgs/{orgId} prefix); the organization comes from the token. An agent token checks its own permissions; an admin credential may name another subject:

curl -X POST https://app.lumoauth.dev/api/v1/authz/check \
-H "Authorization: Bearer AGENT_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"permission": "document.summarize"}'

# As an admin, on behalf of a specific agent:
# -d '{"permission": "document.summarize", "subject": {"type": "agent", "id": "AGENT_ID"}}'

Step 5: Request JIT Permissions (Optional)​

For sensitive operations, request just-in-time permission rather than holding standing permissions. The request is described as structured authorization details (the resource, the actions and the identifier) rather than a coarse scope string. Low-risk requests are auto-approved; high-risk requests enter a human-in-the-loop approval flow:

# Python — using the `lumoauth` SDK
from lumoauth import LumoAuthAgent, JITContext

agent = LumoAuthAgent()
agent.authenticate()

jit = JITContext(agent)
jit.create_task(name="month-end-invoices")

result = jit.request_permission(
{"type": "payment", "actions": ["authorize"], "identifier": "INV-7741"},
justification="Processing end-of-month vendor invoices",
ttl=600,
)

if result["status"] == "approved":
token = jit.get_token(result["request_id"])
# ...use `token` for the single authorized operation

LumoAuth notifies a human approver and returns the decision in real time. The granted token is scoped to that single operation and expires automatically. See JIT Permissions for the full flow.


Step 6: Framework Integrations​

Jump straight to a framework-specific guide:

FrameworkGuide
LangChain / LangGraphRegistry · JIT
CrewAIRegistry · JIT
OpenAI Agents SDKRegistry · JIT
AgnoRegistry · JIT
Google ADKRegistry · JIT

What's Next​

TopicDescription
AI Access ControlFull documentation of AI access control for AI agents
AAuth ProtocolDetailed spec of the AAuth protocol
Ask APINatural language authorization for LLMs
JIT PermissionsHuman-in-the-loop approval flows
Chain of AgencyToken exchange for agent delegation
Workload FederationFederate with workload identities in the cloud
MCP ServersSecure Model Context Protocol servers

Standards used on this page​

Plain nameStandard
Rich authorization requestsRFC 9396

See the standards reference for what each one covers and how LumoAuth uses it.