> For the complete documentation index, see [llms.txt](https://docs.flowcp.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.flowcp.ai/guides/configure-oauth.md).

# Configure MCP Auth

FlowCP authenticates end users with your API's OAuth 2.0 provider. This guide walks through setting up the OAuth provider configuration so your MCP server can act on behalf of real users.

An app can have **multiple named OAuth configs**, and you assign **one config to each app branch** on the [Branches page](/guides/manage-branches.md). A config can serve many branches, but each branch uses exactly one config. This lets, for example, a staging branch point at a staging identity provider while the live branch uses production credentials.

> **Note:** [Changing an app's provider](/guides/connect-your-api.md#changing-an-apps-provider) (for example, OpenAPI → Git) **resets the app's OAuth configuration** — credentials and URLs are provider-specific. After switching, set up OAuth again here and assign a config to each branch.

FlowCP supports OAuth for any provider, with first-class support for **Bubble**, **Auth0**, **Clerk**, **Okta**, **Microsoft Entra ID**, **AWS Cognito**, **Keycloak**, **WorkOS (AuthKit)**, **Zitadel**, **FusionAuth**, **Ory Hydra**, **Frontegg**, **Descope**, **Stytch**, and **Supabase**, plus a **Generic OAuth2** option for everything else. You pick a provider in each OAuth config; the rest of the setup (Client ID, Client Secret, redirect URI) is the same. Depending on the provider you supply a few provider-specific fields (see [Provider-specific settings](#provider-specific-settings)):

* **Bubble** — FlowCP derives the authorize/token URLs from your Bubble app.
* **Auth0** — enter your Auth0 **domain** (`tenant.auth0.com` or a custom domain) and, optionally, an **audience** and **organization**.
* **Clerk** — enter your Clerk **domain** (Frontend API).
* **Okta** — enter your Okta **domain** and, optionally, a custom **Authorization Server ID**.
* **Microsoft Entra ID** — enter your **directory (tenant) ID**.
* **AWS Cognito** — enter your user pool **region** and **User Pool ID** (and, optionally, a Hosted UI **domain**).
* **Keycloak** — enter your **base URL** and **realm**.
* **WorkOS (AuthKit)**, **Zitadel**, **FusionAuth**, **Ory Hydra**, **Frontegg** — enter the provider **domain** (FlowCP discovers the rest).
* **Descope** — enter your **project ID**.
* **Stytch** — enter your **project ID** and pick the **environment** (Live or Test).
* **Supabase** — enter your **project ref** (the `{ref}.supabase.co` subdomain).
* **Generic OAuth2** — enter the `authorize` and `token` URLs directly.

## Authentication modes

OAuth is the default and recommended mode, but not every API offers per-user OAuth. Each branch picks an **Authentication** mode on the [Branches page](/guides/manage-branches.md):

| Mode              | How the upstream call is authenticated                                  | Per-user context |
| ----------------- | ----------------------------------------------------------------------- | ---------------- |
| **User OAuth**    | The end user's own OAuth access token, forwarded on every call.         | ✅ Yes            |
| **API key**       | A shared static key you configure, sent as a header or query parameter. | ❌ No             |
| **Static bearer** | A shared static bearer token you configure.                             | ❌ No             |
| **None**          | No credential is sent. All calls are unauthenticated.                   | ❌ No             |

> **Shared-credential modes (API key, Static bearer, None) relax the per-user guarantee.** Every end user acts through one identity, so FlowCP cannot scope calls to individual users or attribute audit-log entries to them. Use these modes only for APIs that have no per-user OAuth, and prefer **User OAuth** whenever it is available. See [User-context OAuth](/security/user-context-oauth.md).

The static key/token is encrypted at rest with AES-256-GCM, never returned after saving, never written to logs, and never cached. Switching a branch back to **User OAuth** clears any stored static credential.

You create configs for any of these modes on the **MCP Auth** page: click **Add config** and pick the auth type first (User OAuth, API key, Static bearer token, or No authentication). User OAuth opens the provider editor described below; the shared-credential modes open a short form for the credential and (for API key) its header/query placement. Each saved config can then be assigned to one or more branches on the [Branches page](/guides/manage-branches.md). The rest of this guide covers **User OAuth**.

## How the OAuth flow works

1. An end user's AI client connects to FlowCP's MCP server.
2. FlowCP checks whether the AI client has included a valid access token.
3. If not, FlowCP returns an authorization challenge. The AI client redirects the user to FlowCP's OAuth endpoint.
4. FlowCP redirects the user to your provider's authorization page.
5. The user logs in with your provider and grants access.
6. The provider redirects back to FlowCP with an authorization code.
7. FlowCP exchanges the code for access and refresh tokens and returns them to the AI client as an encrypted auth code.
8. The AI client holds the access token and presents it on every subsequent MCP tool call — FlowCP forwards it directly to your API.
9. When the token expires, your API returns 401 and FlowCP signals the AI client to re-authorize automatically.

## Step 1 — Enable OAuth with your provider

Set up an OAuth 2.0 client (sometimes called an "app" or "integration") with your API provider and note the **Client ID**, generate a **Client Secret**, and find the **authorize** and **token** endpoint URLs. Add the FlowCP redirect URI as an allowed redirect URL (see Step 2).

**Bubble:** Go to **Settings → API**, enable **3rd Party OAuth / SAML Access**, note the **Client ID**, and generate a **Client Secret**. FlowCP derives the Bubble authorize/token URLs automatically, so you do not enter them by hand.

## Step 2 — Get the FlowCP redirect URI

In the FlowCP dashboard, open your app and click **MCP Auth** in the sidebar.

The **OAuth redirect URI** field shows a read-only URL like:

```
https://api.flowcp.io/oauth/callback
```

Use the copy button next to the field to grab the URL, then paste it into your provider's allowed redirect URLs list.

## Step 3 — Add an OAuth config

On the same **MCP Auth** page in FlowCP, click **Add config**, choose **User OAuth** as the auth type, and:

1. Enter a **Name** to tell this config apart from others (e.g. “Production”, “Staging”).
2. Choose your **provider** (Bubble, Auth0, Clerk, Okta, Microsoft Entra ID, AWS Cognito, Keycloak, WorkOS/AuthKit, Zitadel, FusionAuth, Ory Hydra, Frontegg, Descope, Stytch, Supabase, or Generic OAuth2).
3. Enter the **Client ID** from your provider.
4. Enter the **Client Secret** (treated as a write-only field — once saved, the value is not displayed again).
5. Fill in the provider-specific fields below.
6. Click **Create config**.

Repeat **Add config** for each distinct set of credentials you need. FlowCP encrypts each client secret at rest using AES-256-GCM.

> **The first config you add is assigned to every branch that doesn't yet have one.** After that, assign configs to branches yourself on the [Branches page](/guides/manage-branches.md).

> The **provider** selector appears for apps connected via a Generic OpenAPI/Swagger URL, letting you choose Generic OAuth2, Auth0, Clerk, Okta, Microsoft Entra ID, or AWS Cognito. Apps connected as **Bubble** are fixed to the Bubble provider — there is no selector, and the authorize/token URLs are derived for you.

## Step 4 — Assign configs to branches

Open **Settings → Branches**. Each branch has an **Authentication** dropdown. With **User OAuth** selected (the default), an **OAuth config** dropdown lists your configs — pick the one that branch should authenticate with. A branch left on **None — not configured** will not authenticate — its published MCP endpoint returns an error until you assign a config. To use a shared API key or bearer token instead, pick that mode and either assign an app-level config you created on the **MCP Auth** page or enter the credential inline (see [Authentication modes](#authentication-modes)).

See [Managing branches](/guides/manage-branches.md) for details.

## Provider-specific settings

| Provider               | Required fields            | Optional fields                                                                     | Default scopes                              |
| ---------------------- | -------------------------- | ----------------------------------------------------------------------------------- | ------------------------------------------- |
| **Bubble**             | — (URLs derived)           | `scopes`                                                                            | —                                           |
| **Auth0**              | `domain`                   | `audience`, `orgId` (Organizations)                                                 | `openid profile email`                      |
| **Clerk**              | `domain`                   | `sameDomain` (use the `__session` cookie)                                           | `openid profile email`                      |
| **Okta**               | `domain`                   | `authServerId` (custom Authorization Server), `classic` (Okta Classic / Org server) | `openid profile email okta.users.read.self` |
| **Microsoft Entra ID** | `tenantId`                 | `scopes`                                                                            | `openid profile email offline_access`       |
| **AWS Cognito**        | `region`, `userPoolId`     | `domain` (Hosted UI domain override), `scopes`                                      | `openid profile email`                      |
| **Keycloak**           | `baseUrl`, `realm`         | `scopes`                                                                            | `openid profile email`                      |
| **WorkOS (AuthKit)**   | `domain`                   | `scopes`                                                                            | `openid profile email offline_access`       |
| **Zitadel**            | `domain`                   | `scopes`                                                                            | `openid profile email offline_access`       |
| **FusionAuth**         | `domain`                   | `scopes`                                                                            | `openid profile email offline_access`       |
| **Ory Hydra**          | `domain`                   | `scopes`                                                                            | `openid profile email offline_access`       |
| **Frontegg**           | `domain`                   | `scopes`                                                                            | `openid profile email`                      |
| **Descope**            | `projectId`                | `scopes`                                                                            | `openid profile email`                      |
| **Stytch**             | `projectId`                | `environment` (`live` / `test`), `scopes`                                           | `openid profile email`                      |
| **Supabase**           | `projectRef`               | `scopes`                                                                            | `openid profile email`                      |
| **Generic OAuth2**     | `authorizeUrl`, `tokenUrl` | `userInfoUrl`, `issuer`, `scopes`                                                   | —                                           |

For every provider except Bubble and Generic OAuth2, FlowCP resolves the authorize, token, and userinfo endpoints automatically from the provider's OIDC discovery document, so you only enter a few identifiers:

* **Auth0 / Clerk / Okta / WorkOS (AuthKit) / Zitadel / FusionAuth / Ory Hydra / Frontegg** — discovery runs against `https://{domain}/.well-known/openid-configuration`.
* **Microsoft Entra ID** — discovery runs against the v2.0 endpoint `https://login.microsoftonline.com/{tenantId}/v2.0/.well-known/openid-configuration`. `tenantId` accepts a directory GUID, a verified domain, or one of `common` / `organizations` / `consumers`. The default scopes include `offline_access`, which Entra requires before it will issue a refresh token.
* **AWS Cognito** — discovery runs against the user pool issuer `https://cognito-idp.{region}.amazonaws.com/{userPoolId}`, which advertises your Hosted UI endpoints. Set the optional **Hosted UI domain** only to override the resolved endpoints. Cognito app clients that have a secret authenticate to the token endpoint with HTTP Basic automatically.
* **Keycloak** — discovery runs against the realm issuer `{baseUrl}/realms/{realm}/.well-known/openid-configuration`. Include an `/auth` path segment in `baseUrl` for legacy (≤16) deployments. Confidential clients authenticate to the token endpoint with HTTP Basic automatically.
* **Descope** — discovery runs against `https://api.descope.com/{projectId}`. Your Descope project ID is also the OAuth client ID.
* **Stytch** — discovery runs against `https://{api|test}.stytch.com/v1/public/{projectId}` (Connected Apps). Pick **Live** (`api.stytch.com`) or **Test** (`test.stytch.com`) with the environment field.
* **Supabase** — discovery runs against `https://{projectRef}.supabase.co/auth/v1/.well-known/openid-configuration` (the Auth OAuth 2.1 server). Confidential clients authenticate to the token endpoint with HTTP Basic automatically.

Okta additionally validates the returned ID token's `nonce` to protect the login round trip.

For **Generic OAuth2**, the `authorizeUrl` and `tokenUrl` you enter are authoritative — FlowCP always uses them directly. The optional `issuer` is a **fallback only**: it is used to discover endpoints (via its `/.well-known/openid-configuration`) when you leave the explicit URLs blank, and is otherwise retained only as token metadata. It never overrides URLs you have entered.

> **Do not set `issuer` to FlowCP's own origin.** Discovery against FlowCP resolves to its `/oauth/authorize` endpoint (the AI-client authorization server, not your upstream IdP) and creates a redirect loop that fails with `unknown client_id`. To use the FlowCP platform identity provider, set `authorizeUrl` to `…/id/authorize` and `tokenUrl` to `…/id/token` and leave `issuer` blank. FlowCP rejects a same-origin `issuer` (and same-origin `/oauth/*` authorize/token URLs) at save time.

## Token refresh

FlowCP supports the OAuth2 **`refresh_token`** grant, so AI clients keep working without re-prompting the user. When an MCP client's access token nears expiry it silently exchanges its refresh token at the token endpoint; FlowCP resolves the server's provider and refreshes through it (for your own provider this requires that the provider issues refresh tokens — e.g. Auth0/Okta with `offline_access`). The refresh token FlowCP issues carries its own server context, so refresh works the same whether the client calls the generic `/oauth/token` endpoint or the per-server `/oauth/mcp/:serverId/token` endpoint — no client configuration is needed and nothing is stored server-side, the AI client holds the tokens. If a refresh token is invalid or expired, FlowCP returns `invalid_grant` and the client simply re-runs the normal authorization flow.

## Custom domain (Bubble, Live)

If your Bubble app has a **custom domain** configured (detected automatically when you connect, or set under **Settings**), FlowCP sends users to Bubble OAuth on that domain for **Live** MCP servers — for example `https://inova.us/api/1.1/oauth/authorize` instead of `*.bubbleapps.io`. This avoids cross-domain login loops.

The FlowCP redirect URI (`https://api.flowcp.io/oauth/callback`) does not change. Register it with your provider as usual.

## Updating credentials

To rotate a client secret:

1. Generate a new secret with your provider.
2. On the FlowCP OAuth page, click **Edit** on the config you want to rotate.
3. Enter the new secret in the **Client Secret** field. Leave the **Client ID** unchanged. (Leaving the secret blank keeps the existing one.)
4. Click **Save changes**.

New authorizations will use the new credentials. Users who have already authorized will continue working until their current access token expires, at which point the AI client will automatically trigger a new authorization using the updated credentials.

## Deleting an OAuth config

Click the **trash** icon on a config to remove it. Any branches assigned to that config become **unassigned** and stop authenticating until you assign them another config. After deletion, no new authorizations can be completed with those credentials, and existing AI client sessions stop working once their current token expires.

## After changing an app's provider

[Changing an app's provider](/guides/connect-your-api.md#changing-an-apps-provider) preserves your existing OAuth configs — they are attached to the app, not to the branches it re-creates, and the new branches re-inherit the app's default config. Review them afterwards, though: the appropriate OAuth provider differs between Bubble apps (which default to the **Bubble** provider) and generic OpenAPI or Git apps (which default to **Generic OAuth2**). Update or [add a config](#step-3--add-an-oauth-config) so it matches the API you switched to.

## UserInfo endpoint

FlowCP exposes a per-server **userinfo endpoint** so an AI client can resolve the connected account behind an access token:

```
GET https://api.flowcp.io/oauth/userinfo/mcp/{serverId}
Authorization: Bearer <access token>
```

The endpoint is advertised as `userinfo_endpoint` in each server's authorization-server discovery document (`/.well-known/oauth-authorization-server/mcp/{serverId}`), so OIDC-aware clients pick it up automatically. It is **per-server** because a bare access token doesn't say which OAuth config issued it — the `serverId` resolves the config and provider endpoints. The global discovery document does not advertise a userinfo endpoint.

FlowCP forwards the token to your provider's own userinfo endpoint and returns the normalized fields `sub`, `email`, and `scope`. It never stores the token.

> **Bubble apps:** Bubble does not expose a userinfo endpoint by default, so this route returns `userinfo_unsupported` for Bubble configs unless you set a `userInfoUrl` in the config metadata. It works out of the box for **Auth0**, **Clerk**, **Okta**, **Microsoft Entra ID**, **AWS Cognito**, **Keycloak**, **WorkOS (AuthKit)**, **Zitadel**, **FusionAuth**, **Ory Hydra**, **Frontegg**, **Descope**, **Stytch**, **Supabase**, and the **Generic OAuth2** provider (with a discoverable or configured `userInfoUrl`).

## Agent authentication discovery

Once a server uses user-context OAuth, FlowCP automatically advertises how AI agents can authenticate to it via the [auth.md](/guides/agent-auth-discovery.md) protocol — no extra setup required.
