The route admits a caller. Each node still has to respect what that subject may do. The sample graph has one node, respond, and it only formats subject:<id>. It does not call another tool.
Read the subject from state
def respond(state: State) -> dict[str, str]:
return {"reply": f"subject:{state['subject']}"}state["subject"] was copied from userinfo inside the route. A node that needs a privileged action should see that string and refuse when the subject is missing or not allowed. Do not re-read the Authorization header inside the node. The graph was compiled once and can be invoked from anywhere. The header is only present on the HTTP request, and only the route is supposed to see it.
If you add a tool node, treat it like any other API call:
| Check | Where |
|---|---|
| Bearer accepted | require_auth on /invoke, before ainvoke |
| Allowed tools | Trust-store row, if your process enforces it. This sample does not. |
| The action itself | Authorization in the node, using state["subject"] |
A reply of subject:<id> means the route accepted the bearer. It is not an allow list. Revoke in Agents stops new tokens when the subject is the OIDC client id. Tokens already issued last until expires_in unless the route's userinfo check, or a trust-store check you add, rejects them.
When the token is an RFC 8693 delegation, put the user id in subject and keep the acting agent beside it if the node needs both. Authorizing the agent as the user skips the delegation.
Related
| Read | To learn how to |
|---|---|
| Custom channel auth for LangGraph | Stop unauthenticated invokes |
| Enriching instructions for LangGraph | Give a model the subject from state |
| Authorization | Pick the check the node should call |
| Agents | Revoke the caller and set allowed tools |