Downstream Credential Access

When a service holds an inbound Descope access token and needs to call a different downstream API, it usually needs a credential scoped to that target, not the token it already has. That applies to MCP servers fetching third-party OAuth tokens, MCP tools calling internal APIs registered as separate Resources, and any middleware that brokers access on an agent's behalf.

This page covers the server-side pattern: validate the inbound token, exchange it at the Descope STS (RFC 8693), and call the downstream service with the outbound credential.

For the MCP-specific walkthrough (tool handlers, sequence diagrams, and Python SDK shortcuts), see Calling downstream APIs from MCP tools.

Two Tokens: Inbound and Outbound

Every exchange involves two tokens, and keeping them straight makes the rest of this page easier to follow:

  • Inbound token: the access token your service receives from the agent or MCP client. Its aud is your service (your Resource), and it carries who is calling and the scopes they consented to. You validate it, but you never forward it downstream.
  • Outbound credential: what Descope hands back for the downstream call. It is either a Connection token (an OAuth token or API key from the Connections vault) or a new Resource token (a Descope JWT minted with a different audience, for another Descope Resource). This is what you actually send downstream.

Choose a Path

Downstream targetPatternWhat you receive
A Descope Resource that accepts the inbound token's audience and scopesPassthroughUse the inbound access token as-is
An internal API registered as a different Descope ResourceSTS → ResourceA Descope JWT scoped to that resource's aud and scopes
A third-party OAuth service or API-key-based serviceSTS → ConnectionThe stored OAuth access token or API key from Connections

Passthrough applies when the downstream service is the same Resource the caller already has a token for, or when its audience and scope requirements match the inbound token. For most tool handlers you should exchange the token rather than forward it. Passthrough falls short when:

  • The inbound token's aud is your service, but the downstream target expects its own audience or a Connection token from the Connections vault, not your token.
  • Policy is evaluated once at your ingress, so you cannot apply per-target rules (the user, the tenant, the calling client, and your server) at the downstream hop.
  • The audit log records no distinct exchange event for each downstream target the tool reached.
  • The full inbound scope set is exposed downstream instead of a credential downscoped for that call.

STS → Resource and STS → Connection use the same token exchange request. The resource parameter tells Descope which path to take. See Using a Resource Token for Resource-to-Resource exchange, and Fetching Connection Tokens for Connection retrieval APIs and SDK methods.

Your Server Is Both a Resource and a Client

To broker downstream access, a single server plays two Descope roles at the same time. It is both a Resource and a Client:

  • As a Resource (the inbound side), it receives the caller's access token, whose aud is your service URL, and validates that JWT against Descope's JWKS.
  • As a Client (the outbound side), it authenticates to the STS with its own client_id and client_secret to exchange the inbound token for the outbound credential.

The same process wears both hats:

The separate client ID (within the server) tells the STS which system is making the exchange, so that:

  • Policies evaluate against the full context, covering the original user, the calling client, and your server
  • The audit log records the complete delegation chain: user → calling client → your server → downstream service
  • Policies can target your server specifically using client.tags or client.name

Without a registered client, the STS cannot distinguish a legitimate server-side exchange from a caller trying to fetch credentials directly.

Setup

1. Register a Client for Your Server

In the Descope Console, go to Clients and create a client to represent the service making exchanges.

  • Enable the Client Credentials grant type
  • Disable grant types the server will not use
  • Copy the generated Client ID and Client Secret

This client is separate from the agents or MCP clients that call your service. It represents the server itself acting as an OAuth client toward the STS.

2. Store Server Credentials Securely

Store the client ID and secret in your server's environment (or secrets manager). These credentials authenticate every token exchange request.

3. Define What the Server Can Reach

Register each downstream target your code will exchange for:

Downstream targetWhere to configure itWhat the STS returns
Internal API that validates Descope JWTsConnect → Resources as an API ResourceA Resource-scoped Descope JWT
Third-party OAuth or API-key serviceAgentic Identity Hub → ConnectionsThe OAuth token or API key stored in the Connections vault

MCP scopes → Connection scopes

MCP Server Resource scopes are what a user consents to when they authorize your MCP server (for example mcp:read_hubspot_contacts). In the MCP server's scopes config, you map each of those scopes to the downstream Connection scopes it needs (for example crm.objects.contacts.read).

Descope uses that mapping at two points, and you never pick Connection scopes by hand at either:

  • During the connect: when the user links the Connection, Descope requests exactly the Connection scopes that correspond to the MCP server scopes the user consented to, so the token stored in the Connections vault carries the right scopes.
  • At fetch time: when your server fetches the token, Descope reads the scopes on the access token (the same MCP server resource scopes) and automatically returns the stored Connection token with the matching Connection scopes.

MCP Server scopes mapped to Connection scopes in the Descope Console

Configure this mapping in your MCP server configuration underneath the Scopes section, not on the Policies page.

See MCP Server Resource scopes and MCP server settings for more information.

4. Create a Policy for Who Can Access

The scope mapping in step 3 lives on the MCP server config. A Policy is a separate control for who is allowed to reach a target: which agents or clients can access which Resources, and what your server, acting as a client, is allowed to exchange for.

Descope evaluates the policy during token exchange and when fetching a Connection token from the vault with an access token. Without a matching allow policy, the request is denied even when the scope mapping is correct.

Making the Exchange

At request time your server validates the inbound access token, then calls the Descope token endpoint as its registered client. The same request shape covers both STS → Resource and STS → Connection; the resource parameter selects the target.

POST /oauth2/v1/token
Authorization: Basic {base64(client_id:client_secret)}
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&subject_token={inbound_access_token}
&subject_token_type=urn:ietf:params:oauth:token-type:access_token
&resource={resource_url_or_connection_id}

Descope validates your server's credentials, evaluates policy against the inbound token's claims, and returns the outbound credential: either a resource-scoped Descope JWT or a token from the Connections vault.

See the Token API for full parameter reference, including optional scope narrowing and resource indicators.

Audit Trail

Every exchange produces an audit event that records:

  • The original user identity from the inbound token
  • The calling client that initiated the request
  • Your server's client ID that made the exchange
  • The downstream Resource or Connection accessed
  • The policy decision applied

This gives you a traceable record from the user who authorized access, through the agent or client that requested it, to the server that fetched the outbound credential.

Was this helpful?

On this page