Part 1 was about the problem. Every agent shares the same service account, and when something breaks, your logs show one credential did something ā but not which agent, not which human approved it, and not whether anyone approved it at all.
I argued that OAuth 2.0 fixes this by giving each agent its own identity, so the enforcement happens at the API layer instead of hoping your application logic holds up against prompt injection.
This article is the proof.
I took an existing hotel booking assistant and added the identity layer ā agent-scoped tokens, the On-Behalf-Of flow, and a token exchange service for agent-to-agent delegation.
Then I added a taxi booking agent to demonstrate chained delegation across agents. The domain is different from the banking scenario in Part 1, but the identity flow is identical:
- A reasoning agent decides.
- An action agent executes.
- A human authorizes.
- The JWT carries all of it.
The Simple Case: Searching Hotels
Before getting into tokens and delegation, it helps to see what an agent looks like when thereās no security involved at all.
The assistant agent has a tool called SearchHotelsTool. A user asks āfind me a hotel in Colombo for next week,ā the LLM figures out the parameters ā location, dates, number of guests ā and calls the tool. The tool hits the hotel APIās search endpoint. No token, no authorization, no identity. The API is public, and anyone can search.
Hereās the flow:

Nothing interesting happens here from a security perspective ā and thatās the point. The search endpoint is read-only, it doesnāt touch customer records or financial data, so thereās no reason to gate it behind authorization. Not every tool an agent calls needs a token.
But what happens when the user picks a hotel and says ābook itā? Thatās where things change. The booking endpoint modifies data ā it creates a reservation, potentially charges a payment method. The same agent, the same conversation, but now the action is sensitive. The agent shouldnāt be able to do this on its own.
Booking a Hotel: Where Identity Kicks In
The user found a hotel they like and says ābook it.ā Same agent, same conversation ā but now the LLM callsĀ BookHotelToolĀ instead ofĀ SearchHotelsTool, and this is where the flow changes.
BookHotelToolĀ is wrapped in aĀ SecureFunctionToolĀ with an auth configuration that says: this tool needs an OBO token withĀ create_bookingsĀ scope. When SecureFunctionToolĀ sees that configuration, it doesnāt just call the hotel API. It first checks whether it already has a valid token. If it doesnāt ā and on the first booking, it wonāt ā it kicks off the On-Behalf-Of flow.
What happens next is an OAuth 2.0 authorization code flow with PKCE, but from the userās perspective, itās simple: a popup window appears asking them to log in and approve the booking permission. Behind the scenes, the auth manager generates an authorization URL, sends it to the frontend through the WebSocket, and waits. The frontend opens the popup. The user authenticates with the Identity Provider, approves the scope, and the IdP redirects back to the agentās callback endpoint with an authorization code. The agent exchanges that code for a token. The popup closes. The user barely noticed.
The token that comes back is the interesting part. Itās not a generic service account token. Itās an OBO token that looks like this:
{
"sub": "951e42a5-...", // the user who approved the booking
"act": {
"sub": "360909c8-..." // the assistant agent that executed it
},
"aud": "7f2c4d91-...", // the hotel booking API
"scope": "create_bookings"
}
subĀ is the user ā the person who approved the booking in the popup.Ā act.subĀ is the agent ā the assistant thatās actually making the API call.Ā audĀ is the hotel booking API ā this token wonāt work against any other API. AndĀ scopeĀ is justĀ create_bookingsĀ ā nothing else.
SecureFunctionTool injects this token into the toolās arguments, and the tool calls the hotel booking API with it in the Authorization header. The API validates the token, checks the signature against the IdPās public keys, checks that the audience matches itself, checks that the scope includesĀ create_bookings, checks that the token hasnāt expired. If any of these fail, the request is rejected. The agent never gets to make the booking.
Hereās the full flow:

The key thing to notice: the agent didnāt have a standing permission to book hotels. It acquired the permission at the moment it needed it, from a specific user, with a specific scope, for a specific API. The token expires. Next time, the user has to approve again. Thereās no persistent access that could be exploited if the agent gets compromised.
And if youāre the one reviewing the audit logs, you can see exactly who authorized what, which agent executed it, and which API processed it. All from one token.
Agent-to-Agent Delegation: The Taxi Handoff
After the hotel is booked, the assistant agent offers the user an airport taxi. If the user says yes, something more interesting happens ā the assistant agent hands off the task to a separate taxi booking agent.
This is where it stops being a single agent calling an API and starts looking like the three-agent pattern from Part 1. The assistant agent is now playing the role of the reasoning agent ā it decided that a taxi is needed and chose which agent should handle it. The taxi booking agent is the action agent ā it executes the booking. The user is still the human who has to approve the sensitive action.
But thereās a problem. The user already authorized the assistant agent to book the hotel. That OBO token has the assistant agentās identity inĀ act.sub. Now a different agent ā the taxi agent ā needs to call a different API ā the taxi booking API ā on behalf of the same user. The taxi agent canāt reuse the hotel agentās token because the audience is wrong, the scope is wrong, and the agent identity is wrong. But we also donāt want to pop up another login window and ask the user to authorize again from scratch.
This is where token exchange comes in. The assistant agent takes its existing OBO token ā the one the user already approved ā and passes it to a Token Exchange Service. The taxi agent presents this parent token along with its own credentials. The Token Exchange Service validates the parent token against the IdPās public keys, verifies the taxi agentās identity against a registry of known agents, and mints a new token. The new token keeps the userāsĀ subĀ from the original ā the userās authorization carries forward. But it wraps the delegation in a nestedĀ actĀ chain that shows exactly how the authority flowed:
{
"sub": "951e42a5-...", // same user from the hotel booking
"act": {
"sub": "0125c5e2-...", // taxi agent ā the one making this call
"act": {
"sub": "360909c8-..." // assistant agent ā the one that delegated
}
},
"aud": "7f2c4d91-...", // taxi booking API
"scope": "taxi_booking",
"delegation_chain": ["0125c5e2-...", "360909c8-...", "951e42a5-..."]
}
Read theĀ actĀ chain from outside in: the taxi agent (0125c5e2) is acting on behalf of the assistant agent (360909c8), which was acting on behalf of the user (951e42a5). TheĀ delegation_chainĀ field is the same information flattened into a list ā easier to index in a log aggregator.
The taxi booking API validates this token the same way the hotel API did ā signature, audience, scope, expiry. But it also gets something the hotel API didnāt: a full chain showing how the request got here. If an auditor asks āwho authorized the taxi agent to book this ride,ā the answer is in the token: the user authorized the assistant agent, and the assistant agent delegated to the taxi agent. Nobody in the chain acted without authorization.
Hereās the full flow:

Thereās one thing worth calling out. The user never saw a second login popup. They authorized the assistant agent once during the hotel booking, and that authorization carried through to the taxi agent via token exchange. The security didnāt come from asking the user to approve every single step ā it came from the infrastructure enforcing that each agent can only do what its token allows, and the delegation chain recording how the authority was passed.
This is the part that RFC 8693 was designed for. And this is also where most Identity Providers fall short.
The IdP landscape for agent-to-agent delegation is still fragmented ā RFC 8693 is optional, and most providers that claim support donāt implement the full delegation semantics. Iāll cover that in Part 3.
Video demo
Hotel Booking demo:
Chained-Agent demo (Hotel Booking and Taxi Booking): in progress