Authentication
Pass user auth context from your frontend to the agent so it can scope tools, data, and decisions to the signed-in user.
You have a chat surface or a hook driving an agent and you want every agent run to know who the request came from. By the end of this guide, your frontend will forward a token, the runtime will pass it through, and your agent code will read the resulting user info on every turn.
When to use this#
- Multi-tenant apps where the agent reads or writes per-user data.
- Tool gating where some tools should only run for authorised users.
- Audit and billing where every run needs an identity to attribute it to.
- Session-aware UX where the agent's behaviour depends on the user's role or permissions.
If you don't need any of those, skip auth entirely. The agent runs anonymously and the frontend never has to care about tokens.
Frontend#
Pass your token via the properties prop. CopilotKit forwards it to AG2's /chat endpoint as a request header.
import { CopilotKit } from "@copilotkit/react-core/v2";
<CopilotKit
runtimeUrl="/api/copilotkit"
properties={{
authorization: userToken,
}}
>
<YourApp />
</CopilotKit>
Backend#
The backend has two responsibilities: validate the token before the agent dispatches, and thread the resolved user identity into AG2's ContextVariables so tools can read it later.
Start by validating the token on AG2's /chat endpoint. The Authorization header arrives as a normal FastAPI Header(...) parameter:
from fastapi import FastAPI, Header, HTTPException
from fastapi.responses import StreamingResponse
from autogen import ConversableAgent, LLMConfig
from autogen.ag_ui import AGUIStream, RunAgentInput
agent = ConversableAgent(
name="assistant",
system_message="You are a helpful assistant.",
llm_config=LLMConfig({"model": "gpt-5.4-mini"}),
)
stream = AGUIStream(agent)
app = FastAPI()
def validate_your_token(token: str) -> dict:
if token != "valid-token":
raise HTTPException(status_code=401, detail="Unauthorized")
return {"user_id": "user_123", "role": "member"}
@app.post("/chat")
async def run_agent(
message: RunAgentInput,
accept: str | None = Header(None),
authorization: str | None = Header(None),
):
if not authorization:
raise HTTPException(status_code=401, detail="Missing authorization header")
token = authorization.replace("Bearer ", "")
user_info = validate_your_token(token)
# use user_info to scope tools, state, and data access before dispatch
return StreamingResponse(
stream.dispatch(message, accept=accept),
media_type=accept or "text/event-stream",
)
Once the token is validated, AG2's tools can read the user identity straight out of ContextVariables. This is how you make individual tool calls aware of who's asking, without having to thread the user object manually through every helper:
from typing import Annotated
from autogen import ContextVariables
@agent.register_for_llm(description="Return account data for the authenticated user.")
def get_account_data(
context: ContextVariables,
account_id: Annotated[str, "The target account id"],
) -> dict:
user = context.get("auth_user")
if not user:
return {"error": "unauthorized"}
if account_id not in user.get("allowed_accounts", []):
return {"error": "forbidden"}
return {"account_id": account_id, "owner": user["user_id"]}
Tool gating#
The most common reason to wire auth is so individual tools can decline to run. Read the resolved user inside the tool's handler and bail if the role doesn't match:
def delete_record(record_id: str, *, user: User):
if "admin" not in user.permissions:
raise PermissionError("admin role required")
# do the delete
This composes with Human in the loop: gate on auth first, surface a confirmation card next, execute last.
Security checklist#
- Always validate the token on the backend. Never trust the frontend's claim.
- Scope every read and write to the resolved user. Auth context only matters if you actually use it to filter data.
- Don't log raw tokens. Log the resolved user id (or
anonymous) instead. - Use HTTPS in production. The Bearer token is sensitive.
- Refresh strategy. Your frontend is responsible for rotating expired tokens before they reach the agent. CopilotKit doesn't refresh on your behalf.
