LangGraph compiles a graph. It does not authenticate the HTTP caller. The auth boundary is the route that invokes the graph. `require_auth` runs before `ainvoke`. The graph reads the subject from state. It does not read the `Authorization` header.

Getting started, including the runnable sample, is [LangGraph](/docs/agent-frameworks/langgraph).

## The route is the hook

```python
authdog = Authdog(public_key=public_key)
compiled = build_graph()

@app.post("/invoke")
async def invoke(user=Depends(authdog.require_auth)):
    result = await compiled.ainvoke(
        {"subject": subject_of(user), "reply": ""}
    )
    return {"reply": result["reply"]}
```

`Authdog` comes from `authdog.fastapi`. `require_auth` is a FastAPI dependency, so a missing or rejected token never enters `invoke`. `subject_of` reads `id`, then `sub`, from the userinfo user.

Call `graph.invoke` or `ainvoke` only from a route that already depends on `require_auth`. A second route that invokes the compiled graph directly skips Authdog. This sample has one route.

The process that serves the graph holds `PK_AUTHDOG`. It does not hold `AUTHDOG_CLIENT_SECRET`. The caller mints the bearer elsewhere. See [API keys and M2M for LangGraph](/docs/ai/langgraph/api-keys-and-m2m).

## What you should see

`POST /invoke` with no token returns `401`. The body does not contain `subject:`. The same call with a bearer userinfo accepts returns `{"reply":"subject:<id>"}`.

## Related

| Read | To learn how to |
| --- | --- |
| [LangGraph](/docs/agent-frameworks/langgraph) | Run the sample |
| [API keys and M2M for LangGraph](/docs/ai/langgraph/api-keys-and-m2m) | Mint the bearer the route expects |
| [Authorizing tool calls for LangGraph](/docs/ai/langgraph/authorize-tool-calls) | Stop a node the subject may not run |
| [Enriching instructions for LangGraph](/docs/ai/langgraph/enriching-instructions) | Pass the subject in state |
