LangGraph Tutorial (Python): adding authentication for advanced developers
This tutorial shows how to add authentication to a LangGraph app in Python using a real request context, token validation, and per-user authorization checks inside graph nodes. You need this when your graph can access private data, call paid APIs, or make decisions that must be tied to an identified user.
What You'll Need
- •Python 3.10+
- •
langgraph - •
langchain-core - •
fastapi - •
uvicorn - •
pydantic - •A valid bearer token source, such as:
- •a static API key for internal tooling
- •JWTs from your identity provider
- •Basic familiarity with:
- •
StateGraph - •typed state in LangGraph
- •FastAPI request handling
- •
Install the packages:
pip install langgraph langchain-core fastapi uvicorn pydantic
Step-by-Step
- •Start with a typed graph state that carries authenticated user context.
The key pattern is simple: authenticate at the edge, then pass only trusted identity data into the graph state.
from typing import TypedDict, Optional
from langgraph.graph import StateGraph, START, END
class GraphState(TypedDict):
user_id: str
role: str
prompt: str
response: Optional[str]
- •Add a small auth layer that validates the bearer token and returns claims.
For production you would verify JWT signatures and expiration; here we use a deterministic token map so the code runs as-is.
from fastapi import FastAPI, Header, HTTPException
app = FastAPI()
TOKEN_DB = {
"token-alice": {"user_id": "alice", "role": "admin"},
"token-bob": {"user_id": "bob", "role": "user"},
}
def authenticate(authorization: str | None) -> dict:
if not authorization or not authorization.startswith("Bearer "):
raise HTTPException(status_code=401, detail="Missing bearer token")
token = authorization.removeprefix("Bearer ").strip()
claims = TOKEN_DB.get(token)
if not claims:
raise HTTPException(status_code=401, detail="Invalid token")
return claims
- •Build graph nodes that enforce authorization before doing work.
This is where you stop relying on the API layer alone; every sensitive operation should check the authenticated role or user ID again.
def authorize_admin(state: GraphState) -> GraphState:
if state["role"] != "admin":
raise PermissionError(f"User {state['user_id']} is not allowed to run this action")
return state
def generate_response(state: GraphState) -> GraphState:
state["response"] = f"Hello {state['user_id']}. You said: {state['prompt']}"
return state
builder = StateGraph(GraphState)
builder.add_node("authorize_admin", authorize_admin)
builder.add_node("generate_response", generate_response)
builder.add_edge(START, "authorize_admin")
builder.add_edge("authorize_admin", "generate_response")
builder.add_edge("generate_response", END)
graph = builder.compile()
- •Expose the graph through an authenticated FastAPI endpoint.
The endpoint converts request headers into trusted state and then invokes the compiled graph with no unauthenticated fields coming from the client.
from pydantic import BaseModel
class ChatRequest(BaseModel):
prompt: str
@app.post("/chat")
def chat(payload: ChatRequest, authorization: str | None = Header(default=None)):
claims = authenticate(authorization)
result = graph.invoke({
"user_id": claims["user_id"],
"role": claims["role"],
"prompt": payload.prompt,
"response": None,
})
return {
"user_id": result["user_id"],
"role": result["role"],
"response": result["response"],
}
- •Run the app and test both authorized and unauthorized paths.
You want to confirm that valid tokens reach the graph and invalid or underprivileged users fail before any protected logic executes.
# save this file as app.py and run:
# uvicorn app:app --reload
# authorized admin request:
# curl -X POST http://127.0.0.1:8000/chat \
# -H "Content-Type: application/json" \
# -H "Authorization: Bearer token-alice" \
# -d '{"prompt":"show me my account summary"}'
# unauthorized user request:
# curl -X POST http://127.0.0.1:8000/chat \
# -H "Content-Type: application/json" \
# -H "Authorization: Bearer token-bob" \
# -d '{"prompt":"show me all admin accounts"}'
Testing It
Run uvicorn app:app --reload, then send one request with Bearer token-alice and another with Bearer token-bob. The first should return a response from the graph, while the second should fail with a permission error because the node enforces an admin-only rule.
Also test missing or malformed headers to confirm you get a 401. If you later swap the static token map for JWT verification, keep these same tests so you know auth failures happen before any graph execution.
Next Steps
- •Add JWT verification with public-key rotation instead of static tokens.
- •Move auth decisions into LangGraph conditional edges for role-based branching.
- •Attach audit logging to every node invocation so you can trace who triggered what action.
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