showcase/shell-docs/src/content/docs/integrations/langgraph/auth.mdx
CopilotKit supports user authentication for LangGraph agents in two deployment modes:
langgraph dev): the server runs your @auth.authenticate handler on every requestIn both cases the frontend sends the same thing — an Authorization header — and the runtime forwards it to the agent.
sequenceDiagram
participant Frontend
participant Runtime as CopilotKit Runtime
participant Backend as LangGraph deployment / FastAPI endpoint
participant Agent as Graph node
Frontend->>Runtime: Authorization: Bearer user-token
Runtime->>Backend: Forward Authorization header
Backend->>Backend: Validate token (401 if invalid)
Backend->>Agent: Verified user via RunnableConfig
Agent->>Agent: Scope tools and data to that user
Pass your authentication token via the headers prop:
<CopilotKit
runtimeUrl="/api/copilotkit"
headers={{
Authorization: `Bearer ${userToken}`,
}}
>
<YourApp />
</CopilotKit>
The runtime forwards Authorization (and any custom x-* headers) onto the outgoing agent call. Headers the server explicitly configured on the agent win on collision, so a service-to-service credential can never be overridden from the browser.
Note: properties is not an auth channel. The runtime delivers properties to the agent as AG-UI forwardedProps — run payload data — and never converts them into a Bearer header.
For agents deployed to LangGraph Platform (or served by langgraph dev), authentication works out of the box with the @auth.authenticate decorator. The forwarded header arrives as the handler's authorization argument.
# auth.py in your LangGraph Platform deployment
from langgraph_sdk import Auth
auth = Auth()
@auth.authenticate
async def authenticate(authorization: str | None):
if not authorization or not authorization.startswith("Bearer "):
raise Auth.exceptions.HTTPException(status_code=401, detail="Unauthorized")
token = authorization.replace("Bearer ", "")
user_info = validate_your_token(token) # Your validation logic
return {
"identity": user_info["user_id"],
"role": user_info.get("role"),
"permissions": user_info.get("permissions", [])
}
from langchain_core.runnables import RunnableConfig
async def my_agent_node(state: AgentState, config: RunnableConfig):
# Access user from LangGraph Platform authentication
user_info = config["configurable"]["langgraph_auth_user"]
user_id = user_info["identity"]
user_role = user_info.get("role")
# Your agent logic with user context
return state
For complete implementation details, see the LangGraph Platform Authentication documentation.
For self-hosted agents (uvicorn + ag-ui-langgraph), you own validation. Do it at the endpoint that serves the AG-UI stream: add_langgraph_fastapi_endpoint takes one pre-built agent and offers no per-request hook, so replace it with the equivalent route of your own. A FastAPI dependency verifies the forwarded Authorization header, and the resolved user is baked into a per-request agent's config.
from typing import Optional
from ag_ui.core.types import RunAgentInput
from ag_ui.encoder import EventEncoder
from copilotkit import LangGraphAGUIAgent
from fastapi import Depends, FastAPI, Header, HTTPException, Request
from fastapi.responses import StreamingResponse
from src.agent import graph
app = FastAPI()
def current_user(authorization: Optional[str] = Header(default=None)) -> dict:
if not authorization or not authorization.startswith("Bearer "):
raise HTTPException(status_code=401, detail="Missing bearer token")
return validate_your_token(authorization.removeprefix("Bearer ").strip()) # your validation
@app.post("/")
async def run_agent(
input_data: RunAgentInput,
request: Request,
user: dict = Depends(current_user),
):
encoder = EventEncoder(accept=request.headers.get("accept"))
# One agent per request: the verified identity rides on this run only, and
# each request gets its own isolated streaming state.
agent = LangGraphAGUIAgent(
name="sample_agent",
description="Agent with authentication support",
graph=graph,
config={"configurable": {"auth_user": user}},
)
async def event_generator():
async for event in agent.run(input_data):
yield encoder.encode(event)
return StreamingResponse(event_generator(), media_type=encoder.get_content_type())
If your nodes don't need the identity and you only want unauthenticated traffic rejected, you can keep add_langgraph_fastapi_endpoint and hang the dependency off the app instead: FastAPI(dependencies=[Depends(current_user)]). The 401 gate applies, but no user context reaches the run config.
from langchain_core.runnables import RunnableConfig
async def my_agent_node(state: AgentState, config: RunnableConfig):
# Already validated by the endpoint — no raw token in the graph
user = config["configurable"]["auth_user"]
user_id = user["user_id"]
user_role = user.get("role")
# Your agent logic with user context
return state
For agents that run in both environments, read whichever key the environment populated:
from langchain_core.runnables import RunnableConfig
async def my_agent_node(state: AgentState, config: RunnableConfig):
configurable = config.get("configurable", {})
user_id = "anonymous"
user_role = None
# LangGraph Platform / langgraph dev
if "langgraph_auth_user" in configurable:
user_info = configurable["langgraph_auth_user"]
user_id = user_info["identity"]
user_role = user_info.get("role")
# Self-hosted (injected by your AG-UI endpoint)
elif "auth_user" in configurable:
user_info = configurable["auth_user"]
user_id = user_info["user_id"]
user_role = user_info.get("role")
# Your agent logic with user context
return state
@auth.authenticate handlerFor comprehensive authentication patterns, authorization handlers, and security best practices, refer to the LangGraph Platform Authentication documentation.
Token not reaching agent:
Authorization in the headers prop, not in propertiesAuthorization header — server-configured headers win on collisionforwardHeaders policy on the runtime, confirm authorization is still allowed by itInvalid token format:
Bearer prefix yourself in the headers value; nothing adds it for youauthorization.removeprefix("Bearer ").strip())User info not available:
@auth.authenticate handler is properly configured, and read the user from config["configurable"]["langgraph_auth_user"]config={"configurable": {...}} per request — a shared agent built at import time carries no request identityAgentExecutionException: 'LangGraphAGUIAgent' object has no attribute 'execute':
CopilotKitRemoteEndpoint path. Serve the AG-UI endpoint directly, as shown above.Authentication works locally but not in production: