A Valid Token for Which Service?
A token can be authentic and still belong to another API. Follow delegated authority from the caller to the MCP server without borrowing another service’s credentials.
16 sections · 8 questions · 1 coding challenge
The arrow keys move between sections too.
Delegation Needed a Destination
BOTIE
MCP’s HTTP authorisation design uses established authorisation mechanisms and resource binding. Our exercise begins after a trusted validator has checked a token, then asks whether its claims belong at this server. It deliberately leaves cryptography to maintained implementations.
The Signature Is Valid, but the Request Is at the Wrong Door
Botie presents a token issued for a search service to the manuals API. The issuer is familiar and the token has not expired. Should the manuals API accept it?
A credential has a purpose as well as an owner. We will work through that purpose, then follow what happens when an MCP server calls another service on the user’s behalf.
These are invented records and deterministic policy checks. The browser runs no agent, shell command, MCP server or network request. Passing these fixtures establishes only the stated policy behaviour.
Step 1 of 2
The same person can hold different credentials
All records below are teaching fixtures.
| Credential | Audience | Scope | Use at manuals API |
|---|---|---|---|
| Token A | manuals-api | manual:read | Continue to object checks |
| Token B | search-api | manual:read | Deny |
| Token C | manuals-api | manual:list | Deny this read |
Token A
- Audience
- manuals-api
- Scope
- manual:read
- Use at manuals API
- Continue to object checks
Token B
- Audience
- search-api
- Scope
- manual:read
- Use at manuals API
- Deny
Token C
- Audience
- manuals-api
- Scope
- manual:list
- Use at manuals API
- Deny this read
Step 2 of 2
An upstream call needs its own authority
The MCP server should not simply forward the credential it received.
- 1
Client
Token intended for MCP server - 2
MCP server
Validate and authorise this request - 3
Upstream API client
Obtain separate appropriate authority - 4
Upstream service
Validate its own intended credential
An accepted token still needs object authorisation
Both branches are required, and object permissions still follow.
Keep token purpose and operation scope separate
Check the verified token’s issuer, audience and lifetime on one branch and its operation scope on another.
BOTIE
Request route
continue to object checks
Follow the operations
1. Verified claims or null
{ "issuer": "company-id", "audiences": [ "manuals-api" ], "subject": "user-7", "expires": 100, "scopes": [ "manual:read" ] }2. Server-owned policy
{ "issuer": "company-id", "audience": "manuals-api", "requiredScope": "manual:read" }3. Current time
50
4. Check verified token claims
true
5. Check this operation scope
true
6. Continue to object permissions
continue to object checks
7. Result
continue to object checks
These are invented records and deterministic policy checks. The browser runs no agent, shell command, MCP server or network request. Passing these fixtures establishes only the stated policy behaviour.
1. Distinguish transport access from record permission
Authentication establishes the identity represented by a validated credential. Authorisation decides what that identity may do. Audience binding checks whether this service is an intended recipient. A scope narrows operations, while object policy narrows particular records.
A token accepted at the manuals endpoint must not allow every manual. Resolve the tenant from authenticated server context, load the object’s ownership and enforce the actual read grant. Do not take tenant or role claims from model-written tool arguments.
The workshop returns continue to object checks deliberately.
2. Choose the right authorisation flow for the transport
The versioned MCP HTTP specification describes an OAuth-based flow for protected remote resources. Stdio servers use a different local process and credential arrangement. Do not paste an HTTP flow onto stdio and assume the host environment became restricted.
Use maintained server/client libraries and your identity provider’s supported configuration. Verify protocol-version compatibility before deployment. Our links name the 2025-11-25 specification so the requirements are inspectable rather than silently tied to a moving draft.
Record which protocol and library versions your connection actually uses.
3. Validate discovery destinations before following them
Authorisation and resource metadata can lead a client to additional URLs. Verify expected issuers, HTTPS requirements and permitted destinations under the connection policy. Protect discovery fetches against server-side request forgery.
Consider redirects, DNS resolution and private network addresses at the point of connection. A string beginning with an approved hostname is not a hostname check. Avoid automatically treating a new authorisation endpoint supplied by an unreviewed server as trusted.
Discovery helps locate services; it does not grant them authority.
4. Use a verifier before reading claims as facts
A decoded token payload is only data until validation succeeds. Use a maintained library with the issuer’s supported keys or introspection, expected algorithms and intended audience. Validate lifetime and token type as appropriate to the issuer. Plan for key rotation and revoked access.
The workshop receives a verifier-owned claims record or null. It cannot accept a user-supplied dictionary claiming to be verified. Our issuer and audience comparison studies policy after that boundary; it is not a JWT implementation.
Do not replace signature verification with Base64 decoding.
A claim can be readable without being authoritative
This is an ordinary dictionary, not a credential.
unverified = {"audiences":["manuals-api"], "subject":"someone"}
print(unverified["subject"])someoneReading a field proves only that the field exists in the supplied data.
Check yourself
Questions on Parts 1 to 4
Question 1 of 3
What has been established?
Use the information below.
A dictionary decoded from a JWT says subject=user-7. No signature or issuer validation ran.5. Bind the token to this resource and operation
Check intended audience and issuer, then require the exact operation scope and current validity. Avoid accepting another service’s token because it was issued by the same identity provider. A missing scope must not be repaired by asking the model to rename its operation.
MCP clients use resource indicators in the specified authorisation flow. Servers must enforce that received credentials are intended for them. Requests for broader access should follow an explicit authorisation process with the user, not silently widen a cached grant.
A credential’s issuer does not make it universal.
6. Keep upstream credentials separate
An MCP server may need to call a storage or ticket API. That upstream service has its own expected recipient and permissions. Obtain the appropriate upstream authority through the supported flow; do not pass the incoming MCP access token straight through.
A proxy can become a confused deputy when it uses its own broader authority to fulfil a caller’s unapproved request. Enforce the caller’s intended resource and operation before using service credentials. Keep tokens out of logs, prompts and tool-return text.
The proxy must not lend its authority merely because the caller knows a tool name.
7. Treat a session identifier as routing state
A session ID can help associate requests with a continuing interaction. It is not a substitute for authentication. Validate protected inbound requests and bind state to the authenticated principal rather than accepting a user ID from a request argument.
Use unpredictable session identifiers and protect resumed streams and shared queues from cross-user mixing. Test session reuse by a different identity. Returning to a saved thread must not recover permissions that the caller no longer holds.
Continuity of conversation and continuity of authority are different properties.
8. Check the object at the point of use
After transport and scope checks, look up the target under server-controlled tenant and resource rules. Apply the same controls to batch operations, alternate handlers and resumed jobs. An agent can discover an unexpected route through a helper that ordinary UI testing never calls.
Recheck current grants immediately before a consequential operation. Keep missing credentials, expired authority and insufficient object access observable without returning protected content in the error explanation.
Every execution path needs the same object boundary.
9. Test token failures without using real credentials
Use a disposable provider and synthetic users for integration tests. Cover wrong audience, wrong issuer, expiry, missing scope, invalid signature, revoked access and cross-tenant objects. Exercise discovery and redirects under a controlled network fixture.
Our Python challenge checks only the claims-level route. Keep the integration suite responsible for cryptographic and transport behaviour, including the rejection of tokens that merely decode successfully.
A passing claims fixture cannot establish a secure OAuth deployment.
Check yourself
Questions on Parts 1 to 9
Question 1 of 5
The shared issuer alone makes the token acceptable.
Consider this example.
A valid search-api token reaches manuals-api. Both use the same issuer.Try it with Botie
Build with Botie
Write and run Python