> ## Documentation Index
> Fetch the complete documentation index at: https://docs.portkey.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Client Credentials (M2M)

> Connect Portkey to MCP servers that use machine-to-machine OAuth, with no user sign-in.

Some MCP servers don't identify individual users. They trust a **service identity** instead: a client ID and secret issued by an identity provider (IdP), exchanged for a short-lived access token. This is the OAuth 2.0 **client credentials** grant, also called machine-to-machine (M2M) auth.

Configure it once on the MCP integration. Portkey requests tokens, caches them, refreshes them before they expire, and sends them to the MCP server. Agents never see the credentials.

```mermaid theme={"system"}
sequenceDiagram
    participant Agent as Agent / Client
    participant PK as Portkey MCP Gateway
    participant IdP as Your IdP (token endpoint)
    participant MCP as MCP Server

    Agent->>PK: MCP request + Portkey API key
    PK->>IdP: grant_type=client_credentials (client ID + secret)
    IdP->>PK: Access token (expires_in)
    PK->>MCP: MCP request + Authorization: Bearer token
    MCP->>PK: Response
    PK->>Agent: Response
    Note over PK: Token cached and reused until shortly before expiry
```

***

## When to Use

Use client credentials when:

* The MCP server sits behind your own IdP (Keycloak, Microsoft Entra ID, Amazon Cognito, and similar) and accepts service tokens
* Access is to shared resources (knowledge bases, internal APIs, analytics) rather than personal data
* No user should be prompted to sign in to the MCP server

Use [OAuth 2.1](https://docs.portkey.ai/docs/product/mcp-gateway/authentication/oauth.md) instead when each user needs their own access to the server, such as their email or repositories.

| | Client Credentials | OAuth 2.1 (per user) |
| - | - | - |
| **Who the MCP server sees** | One service identity | Each user |
| **User sign-in to the MCP server** | None | Required once per user |
| **Token** | One per server, per workspace | One per user |
| **Best for** | Shared resources, service-to-service | Personal data, per-user attribution |

> [!NOTE]
>
> Agents still authenticate to Portkey separately, with an API key or another [gateway auth method](https://docs.portkey.ai/docs/product/mcp-gateway/authentication.md). Client credentials only covers the second hop, Portkey to the MCP server.

***

## Prerequisites

Set these up at your IdP first:

1. **A confidential client** that can use the client credentials grant, with a client ID and client secret
2. **A scope** the client may request (most IdPs require one). Portkey sends only the scope you configure
3. **The token endpoint URL.** It must use **HTTPS** and be reachable from the gateway
4. **An MCP server** that accepts access tokens from this IdP

Verify the client works before configuring Portkey:

```sh theme={"system"}
curl -s -u "$CLIENT_ID:$CLIENT_SECRET" \
    -d grant_type=client_credentials \
    -d scope="$SCOPE" \
    "$TOKEN_ENDPOINT"
```

The response contains an `access_token` and `expires_in`. If this call fails, Portkey's request fails the same way.

### Where to find the token endpoint

| IdP | Token endpoint | Scope format |
| - | - | - |
| Okta | `https://<okta-domain>/oauth2/<authorization-server-id>/v1/token` (`default` for the default server) | A custom scope you create on the authorization server, such as `mcp.read` |
| Keycloak | `https://<host>/realms/<realm>/protocol/openid-connect/token` | A client scope assigned to the client, such as `mcp.read` |
| Microsoft Entra ID | `https://login.microsoftonline.com/<tenant>/oauth2/v2.0/token` | `api://<app-id>/.default` |
| Amazon Cognito | `https://<domain>.auth.<region>.amazoncognito.com/oauth2/token` | `<resource-server>/<scope>` |

For Okta, create an **API Services** app, add a custom scope to the authorization server, and add an access policy rule that allows the **Client Credentials** grant for that app.

> [!NOTE]
>
> Not every IdP and plan offers the client credentials grant. Okta, for example, offers it only to orgs with an NHI Authentication Token subscription purchased or renewed on or after August 14, 2026. If the token request fails with a message about a missing entitlement, check your IdP plan before debugging Portkey.

***

## Configure in the UI

![Add Server form with OAuth Client Credentials selected, showing Token Endpoint, Client ID, Client Secret and Scope fields](https://mintcdn.com/portkey-docs/iKp8KCN9UteTgbUl/images/mcp-gateway/client-credentials-form.png?fit=max\&auto=format\&n=iKp8KCN9UteTgbUl\&q=85\&s=bbff85b0fba0ba028b18b7207df993af)

**1. Add the server**

Go to **MCP Registry** and click **Add Server**. Enter the server's name, URL, and transport.

**2. Select the auth type**

Under **Authentication**, choose **OAuth Client Credentials**.

**3. Enter the credentials**

| Field | Value |
| - | - |
| **Token Endpoint** | The IdP's token URL. Must start with `https://` |
| **Client ID** | The client's ID |
| **Client Secret** | The client's secret |
| **Scope** | Optional. Space-separated scopes to request, such as `mcp.read mcp.write` |

**4. Provision and create**

Click **Next Step**, choose the workspaces that get access, and create the server.

Token Endpoint and Client ID are required. Client Secret is needed for most IdPs. Scope is optional: when empty, Portkey sends no scope and the IdP applies the client's defaults.

### Store credentials in a secret reference

Each field has a **Direct / Secret Ref** toggle. Switch a field to **Secret Ref** to read its value from your vault instead of storing it in Portkey. Store the client secret there at minimum.

See [Secret References](https://docs.portkey.ai/docs/product/enterprise-offering/secret-references.md) to connect a vault.

> [!WARNING]
>
> Fields set to a secret reference aren't stored as direct values. If a field already had a direct value, switching to a secret reference and saving replaces it.

***

## Configure with the API

Create the integration with `auth_type` set to `oauth_client_credentials` and the credentials under `configurations.oauth_client_credentials`:

```sh cURL theme={"system"}
curl -X POST https://api.portkey.ai/v1/mcp-integrations \
    -H "x-portkey-api-key: $PORTKEY_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
        "name": "Internal Knowledge Base",
        "slug": "internal-kb",
        "url": "https://mcp.example.com/mcp",
        "transport": "http",
        "auth_type": "oauth_client_credentials",
        "configurations": {
            "oauth_client_credentials": {
                "token_endpoint": "https://auth.example.com/oauth2/token",
                "client_id": "portkey-mcp-client",
                "client_secret": "<CLIENT_SECRET>",
                "scope": "mcp.read"
            }
        }
    }'
```

```python Python theme={"system"}
import requests

resp = requests.post(
    "https://api.portkey.ai/v1/mcp-integrations",
    headers={"x-portkey-api-key": PORTKEY_API_KEY},
    json={
        "name": "Internal Knowledge Base",
        "slug": "internal-kb",
        "url": "https://mcp.example.com/mcp",
        "transport": "http",
        "auth_type": "oauth_client_credentials",
        "configurations": {
            "oauth_client_credentials": {
                "token_endpoint": "https://auth.example.com/oauth2/token",
                "client_id": "portkey-mcp-client",
                "client_secret": CLIENT_SECRET,
                "scope": "mcp.read"
            }
        }
    }
)
```

```js JavaScript theme={"system"}
const resp = await fetch("https://api.portkey.ai/v1/mcp-integrations", {
    method: "POST",
    headers: {
        "x-portkey-api-key": PORTKEY_API_KEY,
        "Content-Type": "application/json"
    },
    body: JSON.stringify({
        name: "Internal Knowledge Base",
        slug: "internal-kb",
        url: "https://mcp.example.com/mcp",
        transport: "http",
        auth_type: "oauth_client_credentials",
        configurations: {
            oauth_client_credentials: {
                token_endpoint: "https://auth.example.com/oauth2/token",
                client_id: "portkey-mcp-client",
                client_secret: CLIENT_SECRET,
                scope: "mcp.read"
            }
        }
    })
})
```

### Configuration fields

All fields live under `configurations.oauth_client_credentials`.

| Field | Required | Description |
| - | - | - |
| `token_endpoint` | Yes | IdP token URL. Must be HTTPS |
| `client_id` | Yes | OAuth client ID |
| `client_secret` | Usually | OAuth client secret. Omit only for IdPs that don't require one |
| `scope` | No | Space-separated scopes to request. Nothing is sent when omitted |
| `token_endpoint_auth_methods_supported` | No | API only. How the secret is sent, such as `["client_secret_post"]`. Default is HTTP Basic |
| `issuer` | No | API only. Rarely needed. Defaults to the token endpoint's origin |

### Reference the secret from a vault

Map the secret with `secret_mappings` instead of sending `client_secret`:

```json theme={"system"}
{
    "auth_type": "oauth_client_credentials",
    "configurations": {
        "oauth_client_credentials": {
            "token_endpoint": "https://auth.example.com/oauth2/token",
            "client_id": "portkey-mcp-client",
            "scope": "mcp.read"
        }
    },
    "secret_mappings": [{
        "target_field": "configurations.oauth_client_credentials.client_secret",
        "secret_reference_id": "my-mcp-client-secret"
    }]
}
```

***

## How It Works

**Token requests.** Portkey sends a `POST` to the token endpoint with `grant_type=client_credentials`, plus `scope` when configured. The client ID and secret are sent with HTTP Basic authentication.

**Caching.** Portkey keeps one token per server per workspace and shares it across all users and gateway instances. It doesn't request a new token for every call.

**Refresh.** A token is replaced shortly before it expires, up to 60 seconds early (less for very short-lived tokens). If the IdP doesn't return `expires_in`, Portkey assumes one hour. If the MCP server answers `401`, Portkey requests a new token and retries once.

**Config changes.** Changing the credentials, scope, or endpoint takes effect without a restart, and Portkey requests a new token with the new values. See [Configuration changes aren't taking effect](https://docs.portkey.ai/docs/help-center/mcp-gateway-troubleshooting.md#configuration-changes-aren-t-taking-effect) if a change seems stuck.

### What Portkey sends where

| Request | Goes to | Contains |
| - | - | - |
| Token request | IdP token endpoint | Client credentials and the standard OAuth headers only |
| MCP request | MCP server | `Authorization: Bearer <token>`, plus your [custom headers](https://docs.portkey.ai/docs/product/mcp-gateway/authentication/custom-auth.md), [forwarded headers](https://docs.portkey.ai/docs/product/mcp-gateway/authentication/forwarding-headers.md), and [identity headers](https://docs.portkey.ai/docs/product/mcp-gateway/authentication/identity-forwarding.md) |

Static, forwarded, and identity headers never reach the IdP.

***

## Security

* **HTTPS only.** Token endpoints that don't use HTTPS are rejected, since the secret is sent with every token request.
* **No private addresses.** Token endpoints that resolve to private or reserved network addresses are blocked. On self-hosted and hybrid gateways, allow an internal IdP by adding its host to `TRUSTED_CUSTOM_HOSTS`. See [Custom Hosts](https://docs.portkey.ai/docs/product/ai-gateway/custom-hosts.md#trusted-custom-hosts-allowlist).
* **No silent fallback.** If the client ID or a valid token endpoint is missing, requests to the server fail. Portkey doesn't fall back to static headers.
* **Credential isolation.** Agents see only their Portkey credentials, never the client secret or access token.
* **Shared identity.** Every user of the server acts as the same service identity at the MCP server. Don't use it for per-user permissions or attribution. Use [OAuth 2.1](https://docs.portkey.ai/docs/product/mcp-gateway/authentication/oauth.md) for those.

> [!WARNING]
>
> Client secrets are stored by Portkey unless you use a secret reference. Use a secret reference and rotate the secret at your IdP on a schedule.

***

## Rotate the Secret

1. Create a new secret for the client at your IdP. Keep the old one active
2. Update **Client Secret** in the integration (or the vault entry it references)
3. Wait for the change to propagate, then confirm requests to the MCP server succeed
4. Revoke the old secret

Portkey requests a new token with the new secret. Secret values read from a vault are cached for up to 5 minutes, so the old secret can still be in use until then. Revoking it earlier can make token renewal fail. No agent changes are needed.

***

## Limitations

Portkey sends only `grant_type` and `scope` on the token request.

* **No extra token parameters.** IdPs that require one, such as an `audience` or `resource` parameter, aren't supported yet. Auth0, for example, typically expects an audience
* **Client secret authentication only.** `private_key_jwt` and mutual TLS aren't supported
* **Scopes come from configuration.** A scope named in the MCP server's `WWW-Authenticate` challenge doesn't change the request
* **One identity per server.** The same credentials apply to every user and workspace that uses the integration

***

## Troubleshooting

The MCP client usually shows only a generic connection error. Look for the cause in your IdP's logs, where a failed token request appears.

| Symptom | Likely cause | Fix |
| - | - | - |
| IdP logs a failed client authentication, or returns `invalid_client` | Wrong client ID or secret, or the client isn't confidential | Re-check the values with the `curl` call from [Prerequisites](#prerequisites) |
| `invalid_scope` | The scope isn't allowed for this client | Assign the scope to the client at the IdP, or fix the **Scope** field |
| `unsupported_grant_type`, or `invalid_grant` with an entitlement message (Okta: "The NHI Authentication Tokens SKU is not enabled") | The IdP or plan doesn't allow client credentials | Enable the grant for the client, or check your IdP plan |
| IdP requires `audience` or another parameter | See [Limitations](#limitations) | Use an IdP configuration that doesn't require it |
| Saving is refused: "Token Endpoint must use https" | The endpoint starts with `http://` | Use the HTTPS URL |
| Requests fail right after saving | Token endpoint is missing, not HTTPS, or points to a private address | Fix the endpoint. For an internal IdP on a self-hosted gateway, add it to `TRUSTED_CUSTOM_HOSTS` |
| Token request succeeds, MCP server answers `401` | The server rejects the token: wrong issuer or audience, or a missing scope | Align the MCP server's validation with the IdP's token |
| Works for a while, then fails | Secret was rotated or revoked at the IdP | Update the secret in the integration |

***

## Related

| Topic | Description |
| - | - |
| [Authentication Overview](https://docs.portkey.ai/docs/product/mcp-gateway/authentication.md) | How gateway and server authentication fit together |
| [OAuth 2.1](https://docs.portkey.ai/docs/product/mcp-gateway/authentication/oauth.md) | Per-user authentication to MCP servers |
| [Custom Auth](https://docs.portkey.ai/docs/product/mcp-gateway/authentication/custom-auth.md) | Static headers and API keys |
| [Secret References](https://docs.portkey.ai/docs/product/enterprise-offering/secret-references.md) | Keep credentials in your own vault |
| [MCP Gateway Troubleshooting](https://docs.portkey.ai/docs/help-center/mcp-gateway-troubleshooting.md) | Fix common connection problems |

***

> [card] [Portkey is now PRISMA AIRS AI Gateway. See it in action.](https://www.paloaltonetworks.in/ai-security/ai-gateway?utm_source=portkey\&utm_medium=referral\&utm_campaign=prisma_airs\&utm_content=docs_nav#contact)
>
> Contact Us


## Related topics

- [Authentication](https://docs.portkey.ai/docs/product/mcp-gateway/authentication.md)
- [Add External MCP Servers](https://docs.portkey.ai/docs/product/mcp-gateway/external-mcp-servers.md)
- [Gmail MCP server](https://docs.portkey.ai/docs/integrations/mcp-servers/gmail-mcp-server.md)
- [MCP Advanced Configuration](https://docs.portkey.ai/docs/product/mcp-gateway/advanced-configuration.md)
- [August](https://docs.portkey.ai/docs/changelog/2026/august.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.