# Configuring Amazon Cognito

The MCP Gateway can use Amazon Cognito as the identity provider behind its
downstream OAuth flow. The `mcp-cognito-oauth-inbound` policy is a
Cognito-friendly wrapper around the generic `mcp-oauth-inbound` policy: provide
your AWS region, user pool ID, hosted UI domain, client ID, and client secret,
and the policy derives the OIDC issuer, JWKS URL, and authorize and token URLs
for you.

This guide walks through the Cognito user pool setup, then wires the policy into
a gateway project. Read the [authentication overview](./overview.mdx) first for
the two-layer OAuth model.

:::note

Cognito splits OIDC across two domains: discovery and JWKS are served from the
Cognito IDP service domain (`cognito-idp.{region}.amazonaws.com/{userPoolId}`),
while browser login is served from the **user pool hosted UI domain**. The
wrapper handles both.

:::

## Set up Cognito

### Create or pick a user pool

1. In the AWS Cognito console, open **User pools** and either pick an existing
   pool or create a new one. Note the **User pool ID** (it looks like
   `us-east-1_AbCdEf123`).
2. Under **App integration**, set up a **hosted UI domain**. You can use a
   Cognito-prefix domain like `my-pool.auth.us-east-1.amazoncognito.com` or a
   custom domain like `auth.example.com`. The wrapper takes only the host — no
   scheme, no path.

### Create an app client

1. Under **App integration → App clients**, click **Create app client**.
2. Choose **Confidential client**. The client must have a client secret — the
   gateway needs it for the federated token exchange.
3. Set **Allowed callback URLs** to `https://<gateway-host>/oauth/callback`. Add
   `http://localhost:9000/oauth/callback` for local development.
4. Enable **Authorization code grant** under allowed OAuth flows.
5. Enable the OIDC scopes the gateway needs — `openid`, `profile`, and `email`.
6. Click **Create app client**.

Note the **Client ID** and **Client Secret** from the app client's detail page.

## Wire the policy into the gateway

Add the policy to `config/policies.json`:

```json
{
  "name": "cognito-managed-oauth",
  "policyType": "mcp-cognito-oauth-inbound",
  "handler": {
    "module": "$import(@zuplo/runtime/mcp-gateway)",
    "export": "McpCognitoOAuthInboundPolicy",
    "options": {
      "awsRegion": "us-east-1",
      "userPoolId": "$env(COGNITO_USER_POOL_ID)",
      "userPoolDomain": "$env(COGNITO_USER_POOL_DOMAIN)",
      "clientId": "$env(COGNITO_CLIENT_ID)",
      "clientSecret": "$env(COGNITO_CLIENT_SECRET)"
    }
  }
}
```

:::caution

`userPoolDomain` is the hosted UI host only — no `https://`, no trailing slash,
no path. The policy fails at boot if any of those are present.

:::

Attach the policy to each MCP route in `config/routes.oas.json` and register the
gateway plugin in `modules/zuplo.runtime.ts` (see
[Configuring Auth0](./configuring-auth0.mdx#wire-the-policy-into-the-gateway)
for the route and plugin patterns — they're identical across all wrappers).

## What the wrapper derives

| Generic field           | Derived value                                                                      |
| ----------------------- | ---------------------------------------------------------------------------------- |
| `oidc.issuer`           | `https://cognito-idp.{awsRegion}.amazonaws.com/{userPoolId}`                       |
| `oidc.jwksUrl`          | `https://cognito-idp.{awsRegion}.amazonaws.com/{userPoolId}/.well-known/jwks.json` |
| `browserLogin.url`      | `https://{userPoolDomain}/oauth2/authorize`                                        |
| `browserLogin.tokenUrl` | `https://{userPoolDomain}/oauth2/token`                                            |

## Test the configuration

The fastest sanity check is to connect an MCP client:

1. Open Claude Desktop, Cursor, Claude Code, or another OAuth-aware MCP client.
2. Add a remote MCP server pointing at one of your `/mcp/{slug}` routes.
3. The client should redirect you to the Cognito hosted UI sign-in page. After
   login, the gateway's consent screen renders. Approve it.
4. The client receives an access token and can call `tools/list`.

If something fails partway through, walk the flow manually using the
[manual OAuth testing guide](./manual-oauth-testing.mdx) — it exercises every
endpoint with `curl` so you can see the raw responses.

## Common issues

- **The policy rejects `userPoolDomain` at boot.** The value includes
  `https://`, a trailing slash, or an OAuth path. Strip those — use only
  `auth.example.com` or `my-pool.auth.us-east-1.amazoncognito.com`.
- **Browser login lands on a Cognito error page.** The callback URL on the app
  client doesn't match. Set it to `https://<gateway-host>/oauth/callback`
  exactly.
- **`invalid_client` from Cognito's token endpoint.** The app client doesn't
  have a client secret, or the secret value doesn't match. Cognito confidential
  clients require both ID and secret.

## Related

- [Authentication overview](./overview.mdx)
- [Configuring a generic OIDC provider](./configuring-generic-oidc.mdx)
- [Per-user OAuth to upstream MCP servers](./upstream-oauth.mdx)
