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.
- 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.
| Step | What to enter |
|---|---|
| Who is this agent | Agent name (e.g. summarizer-agent), an optional description, the owner and the framework |
| How it proves its identity | The 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 do | Capabilities (the permissions that this agent may request, picked from a list grouped by resource), plus optional acting-for-people settings and a budget |

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

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'
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:
| Framework | Guide |
|---|---|
| LangChain / LangGraph | Registry · JIT |
| CrewAI | Registry · JIT |
| OpenAI Agents SDK | Registry · JIT |
| Agno | Registry · JIT |
| Google ADK | Registry · JIT |
What's Next
| Topic | Description |
|---|---|
| AI Access Control | Full documentation of AI access control for AI agents |
| AAuth Protocol | Detailed spec of the AAuth protocol |
| Ask API | Natural language authorization for LLMs |
| JIT Permissions | Human-in-the-loop approval flows |
| Chain of Agency | Token exchange for agent delegation |
| Workload Federation | Federate with workload identities in the cloud |
| MCP Servers | Secure Model Context Protocol servers |
Standards used on this page
| Plain name | Standard |
|---|---|
| Rich authorization requests | RFC 9396 |
See the standards reference for what each one covers and how LumoAuth uses it.