AAuth Quickstart — LangChain / LangGraph
Wire AAuth (the agent identity protocol: an agent proves who it is by signing each request with its own private key instead of sending a shared secret) into a LangGraph ReAct agent so every tool call carries cryptographic proof of identity. The agent's identity is an Ed25519 key pair registered with LumoAuth; the agent gets a short-lived access token via the AAuth token endpoint; every outgoing tool call is a signed HTTP request (a per-request, replay-proof proof that the agent holds its key) sent with an Authorization: Bearer <token> header.
- A LumoAuth organization (agent identity is available on every plan, nothing to switch on)
- Python 3.9+
Complete Steps 1–3 first (generate a key pair, register the agent and the resource), then verify with the Python SDK quickstart before wiring LangGraph.
Install
pip install 'lumoauth[aauth]' langchain-openai langgraph
Example
import asyncio
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from langgraph.prebuilt import create_react_agent
from lumoauth import AAuthClient
# 1. Initialize the AAuth client (load existing key from file in production)
private_pem, jwks = AAuthClient.generate_keypair()
aauth = AAuthClient(
agent_identifier="https://my-agent.example.com",
private_key_pem=private_pem,
org_id="acme-corp",
)
# 2. Obtain the agent token and a resource token (see the AAuth Quickstart).
# agent_token is mandatory: the /agent/token endpoint authenticates the
# agent via the Agent-Auth header, so request_authorization requires it.
agent_token = "..." # your `agent+jwt`, bound to this key pair via cnf.jwk
resource_token = "..." # from your resource server
# 3. Request authorization upfront
tokens = aauth.request_authorization(
resource_token=resource_token,
scope="read write",
agent_token=agent_token,
)
if tokens.get("authorization_required"):
raise RuntimeError("User consent required - implement OAuth redirect flow first")
# On success the token endpoint returns the agent access token under the
# key "auth_token" (not "access_token").
ACCESS_TOKEN = tokens["auth_token"]
# 4. Define tools that use the AAuth-issued access token
@tool
def fetch_protected_data(endpoint: str) -> str:
"""Fetch data from the protected API using an AAuth-signed request."""
resp = aauth.signed_request(
"GET",
f"https://api.example.com{endpoint}",
auth_token=ACCESS_TOKEN,
)
if resp.status_code == 200:
return resp.text
return f"Error: HTTP {resp.status_code}"
@tool
def submit_report(payload: str) -> str:
"""Submit a report to the protected API."""
resp = aauth.signed_request(
"POST",
"https://api.example.com/v1/reports",
auth_token=ACCESS_TOKEN,
data={"data": payload},
)
if resp.status_code in (200, 201):
return "Report submitted successfully."
return f"Error: HTTP {resp.status_code}"
# 5. Create and run the LangGraph ReAct agent
tools = [fetch_protected_data, submit_report]
llm = ChatOpenAI(model="gpt-4", temperature=0)
agent_executor = create_react_agent(llm, tools)
def run(query: str):
events = agent_executor.stream(
{"messages": [("user", query)]},
stream_mode="values",
)
for event in events:
event["messages"][-1].pretty_print()
if __name__ == "__main__":
run("Fetch /v1/reports/q4-2024 and then submit a summary as a new report.")
How it works
| Component | Role |
|---|---|
AAuthClient | Manages key material, signs requests, and handles token exchange |
aauth.signed_request(...) | Wraps requests and adds the Signature/Signature-Input request-signature headers plus Authorization: Bearer <token> |
ACCESS_TOKEN | Short-lived token issued by LumoAuth after agent + resource auth |
| LangGraph ReAct loop | Lets the LLM decide which tools to call; AAuth handles auth transparently |
Next steps
- AAuth Protocol spec — full protocol details
- JIT Permissions – LangGraph — add per-operation permission escalation
Standards used on this page
| Plain name | Standard |
|---|---|
| Signed HTTP requests (HTTP message signatures) | RFC 9421 |
See the standards reference for what each one covers and how LumoAuth uses it.