# Connector authentication

Last validated Sep 29, 2026

Connector authentication supplies credentials to upstream MCP servers and HTTP APIs. It is separate from signing in to the Aperture gateway and from configuring credentials for [model providers][docs-model-providers]. This topic explains the authentication model and the `auth` configuration object.

## Shared credentials and per-user authorization

Aperture supports two credential models:

* **Shared credentials**: Bearer tokens, API keys, HTTP Basic authentication, and OAuth 2.0 client credentials authenticate requests with the same credential for every permitted user. Scope these credentials narrowly, preferably read-only.
* **Per-user authorization**: OAuth 2.0 authorization code and dynamic client registration require each user to consent. Aperture stores each user's tokens separately and uses isolated MCP client sessions.

Upstream credentials do not replace Aperture [connector grants][docs-connector-grants]. A user needs both a matching grant and any required upstream authorization. The configured [`Tailnet` connector][docs-tailnet-auth] has a separate, tool-initiated authorization flow for each node it provisions.

## Authentication types

The `auth` object's `type` field selects the authentication scheme. The following table describes the available auth types:

| Type                        | Required fields                           | Description                                                                                                                                                                                                                                                                                                                        |
| --------------------------- | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `bearer_token`              | `secret`                                  | Static `Authorization: Bearer <secret>` header.                                                                                                                                                                                                                                                                                    |
| `api_key`                   | `secret`, `name`, `in`                    | Credential injected as a named header or query parameter. `in` must be `"header"` or `"query"`.                                                                                                                                                                                                                                    |
| `basic`                     | `username`, `password`                    | HTTP Basic authentication.                                                                                                                                                                                                                                                                                                         |
| `oauth2_client_credentials` | `client_id`, `client_secret`, `token_url` | Machine-to-machine OAuth 2.0 token. Auto-refreshed. Optional `scopes` array.                                                                                                                                                                                                                                                       |
| `oauth2_authorization_code` | `client_id`, `auth_url`, `token_url`      | Per-user OAuth 2.0 consent flow. Optional `client_secret` (not required for PKCE-only), `scopes`, `auth_params`, and `redirect_uri`.                                                                                                                                                                                               |
| `oauth2_dcr`                | None                                      | Per-user OAuth 2.0 consent flow using [dynamic client registration][ar-dcr]. Aperture discovers the authorization server and registers a client at runtime, so no `client_id`, `auth_url`, or `token_url` is needed. Optional `url` (defaults to the connector's server-level `url`), `scopes`, `auth_params`, and `redirect_uri`. |

The `auth` block rejects unknown fields. A typo in a field name (for example, `"secert"` instead of `"secret"`) produces a validation error at load time.

## Scheme-specific behavior

### Bearer token

This example supplies a shared bearer token to an MCP server:

```json
{
  "connectors": {
    "servers": {
      "snowflake": {
        "protocol": "mcp",
        "url": "https://mcp.example.com/v1/mcp",
        "auth": {
          "type": "bearer_token",
          "secret": "sk-example-token"
        }
      }
    }
  }
}
```

### API key

This example sends an API key in the `X-API-Key` header:

```json
{
  "connectors": {
    "servers": {
      "weather": {
        "protocol": "http",
        "url": "https://api.weather.example.com",
        "auth": {
          "type": "api_key",
          "secret": "wk-example-key",
          "name": "X-API-Key",
          "in": "header"
        }
      }
    }
  }
}
```

The `in` field must be `"header"` or `"query"`. When set to `"header"`, Aperture injects the credential as a request header with the name specified in `name`. When set to `"query"`, Aperture appends it as a URL query parameter.

### OAuth 2.0 client credentials

This example obtains a shared OAuth token with client credentials:

```json
{
  "connectors": {
    "servers": {
      "analytics": {
        "protocol": "mcp",
        "url": "https://analytics.example.com/v1/mcp",
        "auth": {
          "type": "oauth2_client_credentials",
          "client_id": "aperture-analytics",
          "client_secret": "secret-example",
          "token_url": "https://auth.example.com/oauth2/token",
          "scopes": ["read:data"]
        }
      }
    }
  }
}
```

### Per-user OAuth 2.0 authorization code flow

When a connector uses `oauth2_authorization_code`, each user must complete an individual consent flow. Aperture uses PKCE with the S256 challenge method. Each user gets an isolated MCP client session, so backends that tie session state to a bearer token cannot mix up cross-user state.

#### `auth_params`

The `auth_params` field appends custom parameters to both the authorization URL and the token exchange request body. Some providers require specific parameters to issue refresh tokens:

* **Google**: `{"access_type": "offline", "prompt": "consent"}`. Without `access_type: offline`, Google does not issue a refresh token.

\[Missing snippet: aperture\_google\_workspace\_dpp\_note.mdx]

This example uses your own Google OAuth client with offline access:

```json
{
  "connectors": {
    "servers": {
      "google": {
        "protocol": "mcp",
        "url": "https://mcp.google.example.com/v1/mcp",
        "auth": {
          "type": "oauth2_authorization_code",
          "client_id": "example-client-id",
          "client_secret": "example-client-secret",
          "auth_url": "https://accounts.google.com/o/oauth2/v2/auth",
          "token_url": "https://oauth2.googleapis.com/token",
          "scopes": ["https://www.googleapis.com/auth/spreadsheets.readonly"],
          "auth_params": {"access_type": "offline", "prompt": "consent"}
        }
      }
    }
  }
}
```

### Dynamic client registration (oauth2\_dcr)

`oauth2_dcr` is a per-user OAuth 2.0 flow for MCP servers that support dynamic client registration ([RFC 7591][xt-rfc-7591]). Instead of registering an OAuth application yourself and supplying a `client_id`, `auth_url`, and `token_url`, Aperture discovers the authorization server from the connector's `url` ([RFC 9728][xt-rfc-9728] protected-resource metadata, then [RFC 8414][xt-rfc-8414] authorization-server metadata) and registers a client at runtime.

The `url` field is the MCP server's resource URL. If you omit it, Aperture uses the connector's server-level `url`. Optional `scopes`, `auth_params`, and `redirect_uri` are supported. You do not supply a client ID or secret. Aperture obtains client credentials through registration.

A DCR connector uses the same per-user [connect flow][ar-connect-flow], [token lifecycle][ar-token-lifecycle], and [capability population][ar-capability-population] as `oauth2_authorization_code`. By default, `oauth2_dcr` connectors on a gateway share the callback `https://<aperture-hostname>/aperture/auth/dcr/callback`. A configured `auth.redirect_uri` overrides that default. Some verified DCR providers, such as Vercel, accept only Aperture's public callback. For those providers, a custom `auth.redirect_uri` is not supported. DCR requires upstream support for protected-resource discovery, authorization-server metadata, and dynamic client registration.

This example configures a DCR connector:

```json
{
  "connectors": {
    "servers": {
      "atlassian": {
        "protocol": "mcp",
        "url": "https://mcp.atlassian.com/v1/mcp/authv2",
        "auth": {
          "type": "oauth2_dcr",
          "url": "https://mcp.atlassian.com/v1/mcp/authv2"
        }
      }
    }
  }
}
```

## Per-user connection lifecycle

### Initiating the flow

Connect and disconnect apply only to connectors that use a per-user interactive OAuth flow (`oauth2_authorization_code` or [`oauth2_dcr`][ar-dcr]). The built-in [`Tailnet` connector][docs-tailnet-auth] is an exception. It authorizes through its tools, not through **Connect**.

To start authorization in Chat:

1. Open **Connectors**.
2. Find the connector under **Connect to use**.
3. Select **Connect**.

After provider consent, the provider redirects to the callback selected for the connector. Aperture returns the browser to the connector detail page and shows **Connected.**

Pending authorization flows expire after 15 minutes. If a flow expires, select **Connect** again.

### OAuth callback selection

The callback depends on how the OAuth client is configured:

* **Your own OAuth client**: Register `https://<aperture-hostname>/aperture/auth/<connector-id>/callback` with the provider. The connector ID must match the key under `connectors.servers`. If you set `auth.redirect_uri`, register that URI instead.
* **Supported QuickSetup clients**: When you keep a verified provider's pre-registered Aperture client ID, Aperture uses its managed public callback on gateways with managed callback routing. No manual callback registration is needed. A custom `auth.redirect_uri` is rejected for these managed clients. Use your own OAuth client if you need a custom redirect URI.
* **Dynamic client registration**: Aperture registers the callback with the provider automatically. The default is `https://<aperture-hostname>/aperture/auth/dcr/callback`, shared by DCR connectors on the gateway, rather than a per-connector path. If configured, `auth.redirect_uri` overrides it. Some verified DCR providers, such as Vercel, accept only Aperture's public callback. For those providers, a custom `auth.redirect_uri` is not supported.

Use the callback URL that Aperture shows for your connector.

### Token lifecycle

Aperture stores tokens per user and treats them as expired 60 seconds before their actual expiry, triggering a refresh on the next request. If a token becomes unusable, the connector shows **Needs reconnect**. The user selects **Reconnect** in Chat **Connectors** to authorize again. Selecting **Disconnect** on the connector detail page removes the current user's stored token, so that user must select **Connect** again before using the connector.

### Capability population

Connectors using `oauth2_authorization_code` or `oauth2_dcr` populate their tool catalog for each user after authorization, rather than at startup. The built-in `Tailnet` connector is an exception, because it authorizes through its tools. Aperture fetches capabilities with that user's credentials and caches them per connector and user. Authenticated discovery and tool requests populate the cache if it is empty. Tools from a per-user connector do not appear in `tools/list` for a user until that user has authorized them.

After authorization, the connector detail page in Chat shows available capabilities. Select **Refresh tools** to contact upstream with your credentials and request current tools.

## Set up a connector

Follow the guide for your connector and credential model:

* [Set up an authenticated MCP connector][docs-shared-credentials] for shared credentials.
* [Set up a per-user OAuth 2.0 connector][docs-per-user-oauth] for individual consent.
* [Set up an HTTP API connector][docs-http-connector] for an authenticated API proxy.
* [Add a verified connector][docs-verified-connector] for provider defaults and QuickSetup.

[ar-connect-flow]: #initiating-the-flow

[ar-dcr]: #dynamic-client-registration-oauth2_dcr

[ar-capability-population]: #capability-population

[ar-token-lifecycle]: #token-lifecycle

[docs-connector-grants]: /docs/aperture/connectors/reference#grants-for-connector-resources

[docs-http-connector]: /docs/aperture/how-to/set-up-http-api-connector

[docs-model-providers]: /docs/aperture/set-up-providers

[docs-per-user-oauth]: /docs/aperture/how-to/set-up-per-user-oauth2-connector

[docs-shared-credentials]: /docs/aperture/connectors/set-up-authenticated-mcp-connector

[docs-tailnet-auth]: /docs/aperture/connectors/built-in-connectors#authorization-flow

[docs-verified-connector]: /docs/aperture/connectors/add-verified-connector

[xt-rfc-7591]: https://www.rfc-editor.org/rfc/rfc7591

[xt-rfc-8414]: https://www.rfc-editor.org/rfc/rfc8414

[xt-rfc-9728]: https://www.rfc-editor.org/rfc/rfc9728
