LangGraph Tutorial (Python): adding authentication for intermediate developers
This tutorial shows how to add authentication to a LangGraph app in Python by validating a user token before any agent logic runs. You need this when your graph is exposed through an API, shared across tenants, or connected to tools that should only run for authenticated users.
What You'll Need
- •Python 3.10+
- •
langgraph - •
langchain-core - •
fastapi - •
uvicorn - •
pydantic - •A valid auth token source:
- •for this tutorial, we’ll use a simple bearer token check
- •in production, replace it with JWT validation or your identity provider
- •Basic familiarity with LangGraph state graphs
Install the packages:
pip install langgraph langchain-core fastapi uvicorn pydantic
Step-by-Step
- •Start by defining a graph state that carries both the user message and the authenticated user identity. The key idea is simple: authentication happens before the graph executes, and the verified identity becomes part of the state.
from typing import TypedDict, Annotated
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langchain_core.messages import HumanMessage, AIMessage
class GraphState(TypedDict):
messages: Annotated[list, add_messages]
user_id: str
- •Next, write a small auth function that checks the bearer token and returns a user ID. In production you would verify a JWT signature or call your auth service, but the pattern stays the same: reject unauthenticated requests before they reach the graph.
from fastapi import HTTPException
VALID_TOKENS = {
"token-alice": "alice",
"token-bob": "bob",
}
def authenticate_token(auth_header: str | None) -> str:
if not auth_header or not auth_header.startswith("Bearer "):
raise HTTPException(status_code=401, detail="Missing bearer token")
token = auth_header.removeprefix("Bearer ").strip()
user_id = VALID_TOKENS.get(token)
if not user_id:
raise HTTPException(status_code=401, detail="Invalid token")
return user_id
- •Now build a graph node that uses the authenticated identity while generating a response. This example keeps it deterministic so you can focus on the auth flow rather than model setup.
def assistant_node(state: GraphState) -> dict:
last_message = state["messages"][-1]
content = last_message.content
response = AIMessage(
content=f"Hello {state['user_id']}. You said: {content}"
)
return {"messages": [response]}
graph_builder = StateGraph(GraphState)
graph_builder.add_node("assistant", assistant_node)
graph_builder.add_edge(START, "assistant")
graph_builder.add_edge("assistant", END)
app_graph = graph_builder.compile()
- •Add a FastAPI endpoint that extracts the Authorization header, authenticates it, and passes the verified user into the graph state. This is where authentication becomes enforcement instead of just validation.
from fastapi import FastAPI, Header
from pydantic import BaseModel
app = FastAPI()
class ChatRequest(BaseModel):
message: str
@app.post("/chat")
def chat(request: ChatRequest, authorization: str | None = Header(default=None)):
user_id = authenticate_token(authorization)
result = app_graph.invoke(
{
"messages": [HumanMessage(content=request.message)],
"user_id": user_id,
}
)
return {
"user_id": user_id,
"reply": result["messages"][-1].content,
}
- •If you want to reuse this pattern across multiple graphs, wrap authentication in a dependency-like helper and keep graph nodes free of auth parsing logic. That separation matters because graph nodes should work with trusted state only.
def build_authenticated_state(message: str, authorization: str | None) -> GraphState:
user_id = authenticate_token(authorization)
return {
"messages": [HumanMessage(content=message)],
"user_id": user_id,
}
def run_chat(message: str, authorization: str | None) -> dict:
state = build_authenticated_state(message, authorization)
result = app_graph.invoke(state)
return {
"user_id": state["user_id"],
"reply": result["messages"][-1].content,
}
Testing It
Run the API with Uvicorn:
uvicorn your_module_name:app --reload
Then test with a valid token:
curl -X POST http://127.0.0.1:8000/chat \
-H "Content-Type: application/json" \
-H "Authorization: Bearer token-alice" \
-d '{"message":"show my account summary"}'
You should get back Alice as the user_id, and the reply should include her identity. Try an invalid token next; you should receive a 401 Unauthorized response before LangGraph runs at all.
If you want stronger verification, log inside assistant_node and confirm it only executes after successful auth. In production systems, also test missing headers, expired tokens, and tenant isolation rules.
Next Steps
- •Replace the hardcoded token map with JWT validation using your IdP’s public keys.
- •Add role-based access control by storing
rolesorscopesin graph state. - •Move from direct FastAPI handlers to LangGraph’s server patterns if you want multi-agent workflows behind one API surface.
Keep learning
- •The complete AI Agents Roadmap — my full 8-step breakdown
- •Free: The AI Agent Starter Kit — PDF checklist + starter code
- •Work with me — I build AI for banks and insurance companies
By Cyprian Aarons, AI Consultant at Topiax.
Want the complete 8-step roadmap?
Grab the free AI Agent Starter Kit — architecture templates, compliance checklists, and a 7-email deep-dive course.
Get the Starter Kit