{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreicvkpppgwaidwrlhuxjtp5rnbji6kytj2wcgfuule5474ybvqgmgy",
"uri": "at://did:plc:pgryn3ephfd2xgft23qokfzt/app.bsky.feed.post/3mhuz4own3o32"
},
"path": "/t/best-practices-for-handling-user-identity-in-custom-model-serving-mcp/174594#post_3",
"publishedAt": "2026-03-25T03:47:59.000Z",
"site": "https://discuss.huggingface.co",
"tags": [
"Model Context Protocol",
"GitHub",
"IETF Datatracker",
"Microsoft Learn"
],
"textContent": "Is there no clean solution at the moment…?\n\n* * *\n\nThis is a real gap, and other teams are running into the same thing.\n\nThe short version is:\n\n**MCP now has a much clearer standard for authenticating the client to the MCP server. It does not yet have one fully settled, built-in standard for end-user identity propagation through tool execution and downstream APIs.** The safe pattern today is to authenticate at the edge, derive a verified server-side principal, and then propagate **verified identity context** or a **new downstream token** , not the original raw user token everywhere. (Model Context Protocol)\n\n## Why this feels messy\n\nThere are really **three separate problems** hiding inside one question.\n\nFirst, there is **transport authorization** : can the host client call the MCP server at all? MCP now answers that with OAuth-style authorization for HTTP transports, including Protected Resource Metadata discovery and resource-bound tokens. (Model Context Protocol)\n\nSecond, there is **execution identity** : when a tool runs, which human user and tenant is it acting for? That is the part you care about for tools like `get_balance` and `get_order_status`. MCP discussions show this is still an active area, especially in multi-user setups where one agent serves many end-users and each end-user may have different external tokens or permissions. (GitHub)\n\nThird, there is **downstream delegation** : if your model server or tool runtime needs to call another API, what token or credential should it use there? MCP’s current security guidance is explicit that blind token passthrough is the wrong answer. (Model Context Protocol)\n\nThat is why your experiments feel unsatisfying. Each workaround tends to solve only one of those three layers.\n\n* * *\n\n## What MCP standardizes today\n\nFor the **client → MCP server** hop, the current MCP authorization spec is already fairly strong.\n\nThe client is expected to request a token for the specific MCP server resource, using the OAuth `resource` parameter, and the MCP server must validate that the token was specifically issued for that server. The spec also points to the security guidance explaining why audience validation matters and why token passthrough is forbidden. (Model Context Protocol)\n\nThat means this part is now standard enough:\n\n * the host client authenticates,\n * it obtains a token for the MCP server,\n * it presents that token to the MCP server,\n * the MCP server validates it as the intended resource. (Model Context Protocol)\n\n\n\nSo if your question is “is there a standardized way to pass a token from the host client to the model server,” the answer is **yes, for the MCP server itself**. (Model Context Protocol)\n\nBut that does **not** mean the same token should keep flowing to every internal tool or downstream API.\n\n* * *\n\n## What MCP does not fully standardize yet\n\nWhat is still unsettled is the next layer: **how a multi-user MCP deployment should represent the end-user and delegated actor inside tool execution and downstream service calls**.\n\nThe clearest public evidence is in the ongoing GitHub discussions. Discussion **#234** is explicitly about **multi-user authorization** where one agent serves many end-users and each user may have tokens for multiple external services. Discussion **#483** says the lack of a standard for per-user credentials and downstream authorization leads to inconsistent client behavior and security gaps. Discussion **#804** proposes a gateway-based authorization model, which is another sign the community sees this as an architecture problem not yet fully solved by the core protocol alone. (GitHub)\n\nThere is also a newer open issue in the auth extension work, **ext-auth #13**, which says that even when a downstream system sees `sub` and `client_id`, it may still be unclear whether the request is coming directly from the user or from an autonomous or semi-autonomous agent acting on the user’s behalf. That is an important clue: the remaining gap is not just “which token do I send,” but also “how do I represent user versus agent clearly for authorization and audit.” (GitHub)\n\nSo the honest answer to your third question is:\n\n**yes, there are active discussions and extensions, but no single, final, built-in MCP-native answer yet for full end-user propagation across tool execution and downstream APIs.** (GitHub)\n\n* * *\n\n## The safest practical pattern today\n\nIf I were designing your system now, I would use this pattern:\n\n### 1. Authenticate the host client to the MCP server\n\nThe host client gets a token specifically for your MCP server and sends that to the MCP server. The MCP server validates the token as being intended for itself. This is the clean, standardized part of the flow. (Model Context Protocol)\n\n### 2. Convert that token into a trusted server-side principal\n\nAfter validation, stop thinking in terms of “pass this JWT everywhere.” Instead, derive a server-owned principal object such as:\n\n\n {\n \"sub\": \"user_123\",\n \"tenant_id\": \"acme\",\n \"client_id\": \"webapp-prod\",\n \"scopes\": [\"orders:read\", \"balance:read\"],\n \"trace_id\": \"req_789\"\n }\n\n\nThis is not spelled out as a specific MCP object in the spec, but it is the natural architecture implied by current MCP guidance: the server validates the external token, then uses that validated identity internally rather than treating the token itself as the internal API contract. That direction also matches the “treat the MCP server as an OAuth resource server” discussion in issue **#205**. (GitHub)\n\n### 3. Inject identity into tools from trusted context, not from the model\n\nFor personalized tools, the safest interface is not:\n\n\n get_balance(user_id)\n get_order_status(user_id, order_id)\n\n\nIt is closer to:\n\n\n get_balance()\n get_order_status(order_id)\n\n\nwith the runtime injecting the verified principal from the request context.\n\nWhy? Because once `user_id` becomes model-authored tool input, identity is no longer coming from the auth layer. The model should choose the **operation** and the **business parameters**. It should not choose the **caller identity**. That is exactly the kind of multi-user ambiguity the current MCP discussions are trying to address. (GitHub)\n\n### 4. Authorize inside the tool using subject, tenant, and scope\n\nA secure tool should check:\n\n * whether the caller has the right coarse scope,\n * whether the object belongs to the same tenant,\n * whether the subject is allowed to see that particular record.\n\n\n\nThat matters because scopes alone are coarse. Fine-grained authorization usually still needs subject- and resource-level checks, which is also why proposals like **#483** exist. (GitHub)\n\n* * *\n\n## Why raw token passthrough is the wrong default\n\nThis is the most important security point.\n\nMCP’s security guidance defines **token passthrough** as the anti-pattern where an MCP server accepts a token from the client and passes it on to a downstream API without proper validation and without using the right audience or resource boundaries. The guidance calls out risks like security control circumvention and audit/accountability problems. (Model Context Protocol)\n\nThe spec reinforces that by saying MCP clients must use the `resource` parameter and MCP servers must validate that tokens were issued specifically for them. It explicitly points to the security guide for why token passthrough is forbidden. (Model Context Protocol)\n\nSo the correct distinction is:\n\n * **Client → MCP server** with a token meant for the MCP server: good. (Model Context Protocol)\n * **MCP server → downstream API** using that same token unchanged: bad default. (Model Context Protocol)\n\n\n\n* * *\n\n## What to do for internal APIs versus external APIs\n\nThis split matters a lot.\n\n### Internal APIs you own\n\nFor tools like `get_balance` or `get_order_status`, where the downstream system is your own service, the simplest clean pattern is:\n\n * authenticate the user at the MCP boundary,\n * derive the principal,\n * propagate the principal internally,\n * authorize based on `sub`, `tenant_id`, roles, and scopes.\n\n\n\nIn larger systems, a gateway can mint a short-lived signed internal assertion that backend services trust. The MCP gateway authorization discussion is moving in this direction. (GitHub)\n\n### External APIs or third-party services\n\nFor third-party access, MCP’s current guidance is even stricter. The **Elicitation** spec says third-party credentials must not transit through the MCP client, the MCP server must not use the client’s credentials for that third-party service, and the user must authorize the MCP server directly. It also says the MCP server is responsible for storing and managing those third-party tokens. (Model Context Protocol)\n\nThat means if your tool needs, for example, Google, GitHub, or Slack access on behalf of the user, the right pattern is:\n\n * the user authorizes the MCP server directly,\n * the MCP server stores the resulting tokens,\n * the host client never acts as a generic token relay. (Model Context Protocol)\n\n\n\n* * *\n\n## Where token exchange fits\n\nOnce you have a middle-tier service that needs to call another API, the standard OAuth answer is usually **token exchange** or an **on-behalf-of** flow.\n\nRFC **8693** defines OAuth 2.0 Token Exchange and explains that the client can request a token for a specific downstream resource. It also defines the `act` claim to represent the current actor in a delegation chain, and allows nested `act` claims when multiple delegated hops are involved. (IETF Datatracker)\n\nMicrosoft’s On-Behalf-Of flow is a practical example of the same idea: the client sends token A to API A, API A requests token B for API B, and API B receives token B, not token A. (Microsoft Learn)\n\nThat model maps very well to MCP-like deployments:\n\n * token A is for your MCP server,\n * token B is for the downstream API,\n * the two tokens are not interchangeable. (Model Context Protocol)\n\n\n\nSo if you eventually need true delegated downstream calls, **token exchange is the clean standards-based direction**.\n\n* * *\n\n## Evaluating the workarounds you listed\n\n### Injecting identity data as tool input\n\nThis is the weakest pattern for identity itself.\n\nIt is fine for normal business parameters. It is not ideal for security-critical identity, because it lets the least trustworthy layer in the chain shape who the tool acts as. For personalized tools, identity should come from the validated request context, not from model-generated arguments. That is the underlying concern behind the multi-user authorization discussions. (GitHub)\n\n### Custom pass-through headers\n\nThis can be acceptable **inside** a trusted internal boundary, but only if those headers are set by your gateway or MCP server after validation, not by the host client as an authority signal.\n\nIn other words, custom headers are okay as an internal transport detail. They are not a clean standard for end-user identity propagation across trust boundaries. The gateway-based authorization discussion points toward signed internal assertions instead of loose pass-through metadata. (GitHub)\n\n### Maintaining a session map on the server side\n\nUseful as a correlation mechanism. Not ideal as your primary identity proof.\n\nA session map can help you remember “this conversation is associated with this principal,” but the auth decision should still come from validated tokens or trusted assertions. MCP’s auth tutorial also explicitly warns that `Mcp-Session-Id` is untrusted input and should not be tied to authorization. (Model Context Protocol)\n\n* * *\n\n## What I would recommend concretely\n\nFor your exact case, I would implement this:\n\n### Today\n\nUse the current MCP authorization model for the **host client → model server** hop. Require the host client to present a token with your MCP server as the intended resource, and validate it strictly. (Model Context Protocol)\n\n### Inside your server\n\nDerive a trusted principal object and attach it to request context. Do not let tools accept `user_id` as a model-authored argument for personalized endpoints. Use the principal inside the handler instead. (Model Context Protocol)\n\n### For internal service calls\n\nPrefer a gateway or middleware layer that emits a short-lived internal signed assertion or equivalent trusted context rather than reusing the original client token. This is where the gateway-based authorization direction is most useful. (GitHub)\n\n### For third-party service calls\n\nDo not pass the host client’s external tokens through the MCP layer. Have the user authorize the MCP server directly, and let the MCP server store and manage those provider tokens. (Model Context Protocol)\n\n### For advanced delegation\n\nIf you need multi-hop API access, look at RFC 8693 token exchange and OBO-style flows rather than token passthrough. (IETF Datatracker)\n\n* * *\n\n## Are there plans for built-in identity support?\n\nThere are signs of movement, but the ecosystem is still evolving.\n\nThe official auth-related extensions now include **Enterprise-Managed Authorization** and **OAuth Client Credentials**. Enterprise-managed auth adds an enterprise IdP-centered flow using identity assertions and ID-JAG exchange. OAuth Client Credentials adds machine-to-machine authentication for MCP when there is no user present. (Model Context Protocol)\n\nThere are also active proposals and discussions around:\n\n * multi-user authorization and fine-grained resource control, (GitHub)\n * gateway-based authorization, (GitHub)\n * cryptographic client identity verification via `clientId` and `clientAuth` JWTs in SEP **#1289** , which helps identify the calling software, (GitHub)\n * and the still-open question of distinguishing human users from agents acting for them, as raised in ext-auth **#13**. (GitHub)\n\n\n\nSo the answer is: **yes, there is active work, but no single finished built-in identity-propagation standard yet for every layer of this problem.** (GitHub)\n\n* * *\n\n## Bottom line\n\nThe cleanest current answer is:\n\n**Use standard OAuth-based auth for the host client to authenticate to the MCP server. At the MCP server boundary, validate that token and turn it into a trusted server-side principal. Use that principal for personalized tools. Do not let the model choose identity, and do not blindly pass the original token through to downstream APIs. For downstream calls, use either trusted internal assertions or a separate downstream token obtained via a delegation flow such as token exchange or OBO.** (Model Context Protocol)\n\nThat is the closest thing to a clean, production-safe best practice right now.",
"title": "Best Practices for Handling User Identity in Custom Model Serving (MCP)"
}