LangGraph Tutorial (Python): adding authentication for advanced developers

By Cyprian AaronsUpdated 2026-04-22
langgraphadding-authentication-for-advanced-developerspython

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

  1. 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]
  1. 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
  1. 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()
  1. 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"],
    }
  1. 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

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

Related Guides