# Credential brokering

Canonical URL: https://vercel-mcp-reference.vercel.app/client-side/credential-brokering/
Markdown: https://vercel-mcp-reference.vercel.app/client-side/credential-brokering.md
Audience: engineer, architect, security. MCP spec version: 2026-07-28. Last reviewed: 2026-08-26. Status: stable.

**TL;DR:** A credential handed to a [server](https://vercel-mcp-reference.vercel.app/glossary/#server) is the blast radius if that server is compromised, and servers are untrusted external code. So the host (or the deployment platform) **brokers** credentials: it holds the secrets, gives each server only the narrowly-scoped, short-lived credential its job requires (ideally none), injects it out-of-band at the [trust boundary](https://vercel-mcp-reference.vercel.app/glossary/#trust-boundary), and rotates or revokes it. A server must never see the user's master credentials, another server's secret, a token broader than its task, or, critically, be allowed to **replay** the token the client used to authenticate to *it* against some upstream. On Vercel the brokering machinery has names: environment-scoped and sensitive env vars, [OIDC federation](https://vercel-mcp-reference.vercel.app/glossary/#oidc-federation) instead of static cloud keys, and url-mode [elicitation](https://vercel-mcp-reference.vercel.app/client-side/elicitation/) for third-party user credentials.

## Plain-language explanation

Every integration needs to talk to something: a database, an API, a calendar. The naive move is to hand the server the key. But a server is exactly the component MCP treats as untrusted; a vulnerability or a prompt injection in it now controls whatever its key controls. Credential brokering shrinks that blast radius by keeping the host and the platform in charge of secrets. The broker decides which credential each server gets, scopes it to that server's job, and prefers options where the server holds a weak secret briefly, or no secret at all.

## Where credentials must not live

Three anti-patterns put a secret somewhere the threat model cannot defend:

- **Not in the build or the bundle.** Secrets belong in runtime environment variables, never baked into a deployment artifact or committed to the repo. On Vercel two platform rules make this concrete: anything prefixed `NEXT_PUBLIC_` is inlined into the client bundle and is public by definition (no secret ever carries that prefix), and **sensitive environment variables** are write-only after creation, so a value can be used at runtime but not read back out of the dashboard or API.
- **Not as a tool argument.** Tool arguments are LLM-generated and untrusted, and they flow through the model's context, exactly where a secret must not be. This is the same reasoning as [Identity and principals](https://vercel-mcp-reference.vercel.app/security/identity-and-principals/): identity and secrets are resolved out-of-band, never read from the request payload.
- **Not shared across servers.** One high-privilege credential reused by many servers makes every server as dangerous as the most powerful one. [Least privilege](https://vercel-mcp-reference.vercel.app/patterns/least-privilege/) wants a scoped credential per server, and on Vercel env vars scope per project and per environment (production, preview, development), so a preview deployment never holds production keys.

## Brokering models

From weakest to strongest isolation:

1. **Scoped credential injection.** The deployment provisions a per-server, least-privilege credential (a read-only database connection string, an API key limited to the operations the server exposes) and injects it at runtime as a project-scoped, environment-scoped, sensitive env var. The server holds a *narrow* secret. The `examples/db-adapter-server` (in the repository) read-only credential and the `examples/least-privilege-server` (in the repository) scope model are this idea applied at the server.
2. **Short-lived token exchange.** The workload exchanges its own identity for a downscoped, short-lived token per task. This is Vercel's OIDC federation: each deployment gets a `VERCEL_OIDC_TOKEN`, a signed identity assertion the function exchanges with a cloud provider (for AWS, `AssumeRoleWithWebIdentity`, optionally narrowed further with session policies) for temporary credentials. No static cloud key exists to leak, and a leaked token expires on its own (the function token lives two hours and is delivered per request as the `x-vercel-oidc-token` header, read with `getVercelOidcToken()` from `@vercel/oidc`).
3. **Credential proxy / brokered egress.** The server holds *no* upstream secret at all; it asks the broker to make the privileged upstream call, and the secret is injected at egress. This is the strongest isolation: the secret never crosses the boundary into untrusted code. The [sidecar pattern's Vercel shape](https://vercel-mcp-reference.vercel.app/patterns/sidecar/) applies the same instinct to compute, and `examples/sandbox-isolation-server` (in the repository) shows untrusted work running with no ambient credentials at all inside a [Sandbox](https://vercel-mcp-reference.vercel.app/glossary/#sandbox) egress allowlist.

## Three different credentials, kept separate

An MCP deployment juggles up to three credential populations, and conflating any two of them is a security bug:

1. **The client-to-server token.** MCP [authorization](https://vercel-mcp-reference.vercel.app/security/authorization/) (OAuth 2.1 over Streamable HTTP) governs how a client authenticates *to* a server. On Vercel this is the token `withMcpAuth` hands to your `verifyToken` function. It is audience-bound to that server (RFC 8707 `resource` binding exists precisely for this), and it is scoped *to that server*.
2. **The server's upstream credential.** Provisioned by the deployment for *that integration*, scoped to *that integration*, invisible to the client and the model. This is what the brokering models above provision.
3. **Third-party user credentials.** When a server acts on a user's behalf against an external API (the user's calendar, the user's repo), it obtains those tokens through url-mode [elicitation](https://vercel-mcp-reference.vercel.app/client-side/elicitation/): the user authorizes the server directly, out of band, and the tokens are stored server-side **bound to the verified user identity**. They **MUST NOT** transit the client, and the client's bearer is not a substitute for them.

The rule joining them: **token passthrough is forbidden.** A server **MUST NOT** accept a token that was not issued to it, and must never forward the client's bearer to a downstream API. MCP's Security Best Practices document spells out why: passthrough defeats audience scoping, bypasses the downstream API's rate limiting and monitoring, destroys the audit trail (the upstream sees the wrong identity), and turns the server into a confused deputy for every system that token reaches. Validate audience and scopes on every inbound token; mint or fetch a *different*, narrower credential for every outbound hop.

On the client-to-server side, the broker resolves where to authenticate through discovery: the server advertises OAuth Protected Resource Metadata (RFC 9728) at `/.well-known/oauth-protected-resource` (on Vercel, `protectedResourceHandler` serves it), and the client locates the authorization server from there. The broker should request only the scopes the immediate task needs and rely on **incremental scope elevation** (a `WWW-Authenticate` challenge naming the additional scope when a privileged operation is first attempted) instead of over-requesting up front. Minimal initial scopes keep each token's blast radius as small as the upstream credentials it protects.

One more lifecycle rule arrived with the 2026-07-28 revision (SEP-2352): client credentials the broker persists for these flows are **issuer-bound**. Key them by the authorization server's `issuer` identifier, never present credentials registered with one issuer to a different one, and re-register when a resource's advertised authorization server changes; Client ID Metadata Document identities are the portable exception, since any authorization server resolves the HTTPS `client_id` on demand. Treat a silently swapped issuer as a red flag rather than an inconvenience: it is exactly the shape of a mix-up attack (see [Authorization](https://vercel-mcp-reference.vercel.app/security/authorization/)).

## Rotation and revocation

Brokering is not just provisioning; it is lifecycle. Credentials issued to servers should have **short expiry** and a documented refresh path, and must be **revocable per server** without redeploying everything else. OIDC federation gets this almost for free (the exchanged credentials expire on their own, and revoking the trust relationship cuts off one project), and per-project env vars mean rotating one integration's key touches one project. If a server is compromised, revoking its one scoped credential should contain the incident.

## How the broker holds the map

```mermaid
flowchart TB
    user["User credentials / platform identity (never reach a server)"] --> broker["Broker: host + Vercel env scoping + OIDC"]
    broker -->|"scoped cred A: read-only"| sa["Server A"]
    broker -->|"scoped cred B: send-only"| sb["Server B"]
    sa --> ua["Upstream A"]
    sb --> ub["Upstream B"]
    sa x-.-x sb
```

The user's master credentials and the platform's identity stay at the broker; each server receives only a narrow, task-scoped credential, and no server holds another's. The crossed line is the boundary: server A's credential is not server B's, and neither is the user's.

## Common pitfalls

- **Secret as a tool argument** - puts it in LLM-visible, untrusted context.
- **One shared high-privilege credential** - every server inherits the worst-case blast radius.
- **Secrets in the bundle** - a `NEXT_PUBLIC_` secret is published, not leaked; use runtime env vars, sensitive where supported.
- **Token passthrough** - a server replaying the client's bearer to an upstream; explicitly forbidden by MCP security guidance.
- **Static cloud keys in env vars when OIDC federation exists** - a long-lived secret doing a job short-lived exchange does better.
- **Production credentials visible to preview deployments** - scope env vars per environment; previews are semi-public surfaces.
- **No rotation or per-server revocation** - a leaked credential stays valid, and revoking one means disrupting all.
- **Logging or echoing secrets** - redact in any diagnostic output; an audit log records *which* scope was used, never the key itself.

## Example implementation

The repository demonstrates the **server side** of holding and enforcing scoped credentials; a full host-side broker that provisions per-server credentials is the platform's job (env scoping plus OIDC), not a runnable package:

- `examples/least-privilege-server` (in the repository) - declares the upstream scopes each tool needs and refuses to start if the configured grant has **missing or excess** scopes: the scoping discipline a broker would provision against, enforced in-process with a registration drift guard.
- `examples/auth-server` (in the repository) - the client-to-server leg: `withMcpAuth` token verification, scope-gated tools, and the RFC 9728 metadata endpoint, with the audience checks that make passthrough impossible.
- `examples/db-adapter-server` (in the repository) - a scoped read-only backend credential in practice, plus the output sanitization that keeps upstream internals out of results.

## Related

- [least-privilege](https://vercel-mcp-reference.vercel.app/patterns/least-privilege/) - scoping each credential to exactly the operations a server exposes, and the OIDC federation mapping in full.
- [trust-boundaries](https://vercel-mcp-reference.vercel.app/patterns/trust-boundaries/) - why a credential is the blast radius across each boundary, and which Vercel controls sit on each.
- [Identity and principals](https://vercel-mcp-reference.vercel.app/security/identity-and-principals/) - the parallel rule for identity: resolved out-of-band from the verified token, never from the request.
- [Authorization](https://vercel-mcp-reference.vercel.app/security/authorization/) - the client-to-server OAuth 2.1 leg this page keeps separate from upstream credentials.
- [Elicitation](https://vercel-mcp-reference.vercel.app/client-side/elicitation/) - url mode is the sanctioned channel for third-party user credentials the client must never see.
- [sidecar](https://vercel-mcp-reference.vercel.app/patterns/sidecar/) - isolation for the code that would otherwise hold a credential at all.

## Bibliography

- Model Context Protocol Specification, *Authorization*, version 2026-07-28 - <https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization>
- Model Context Protocol Specification, *Client Registration* (issuer binding), version 2026-07-28 - <https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization/client-registration>
- Model Context Protocol, *Security Best Practices* (token passthrough, confused deputy, scope minimization) - <https://modelcontextprotocol.io/docs/2026-07-28/tutorials/security/security_best_practices>
- Model Context Protocol Specification, *Elicitation* (URL mode third-party authorization), version 2026-07-28 - <https://modelcontextprotocol.io/specification/2026-07-28/client/elicitation>
- IETF RFC 8707, *Resource Indicators for OAuth 2.0* - <https://www.rfc-editor.org/rfc/rfc8707>
- IETF RFC 9728, *OAuth 2.0 Protected Resource Metadata* - <https://www.rfc-editor.org/rfc/rfc9728>
- Vercel Documentation, *Secure backend access with OIDC federation* (token TTL, reuse window, header delivery) - <https://vercel.com/docs/oidc>
- Vercel Documentation, *Sensitive environment variables* - <https://vercel.com/docs/environment-variables/sensitive-environment-variables>
- Vercel Documentation, *Deploy MCP servers to Vercel* - <https://vercel.com/docs/mcp/deploy-mcp-servers-to-vercel>
- OWASP Top 10 for Large Language Model Applications - <https://owasp.org/www-project-top-10-for-large-language-model-applications/>
