Skip to main content

Overview

OAuth on a has two independent halves: Linked accounts replace shared credentials. Requests reach the upstream as the person who made them, with that person’s upstream permissions. Users never see or handle the upstream token. You can enable OAuth on MCP, HTTP, gRPC, and LLM Resources. For remote MCP servers, the MCP Gateway guide covers the full setup, including listeners, hostnames, and client configuration. Linked accounts are sent through a native user. A Linked Account native user sends the requesting user’s linked token instead of a stored credential, and the Resource’s native user selection picks it like any other native user. Linked Account native users need native_users_v3_enabled on the Resource and Connector 2.25.14 or later.

How the Connector picks the linked account

The Connector sends the linked account of the human the request is for: An end user asserted in a request header, such as X-Formal-End-User-Identity, never selects the linked account. It only sets the for policies and logs.
A federation token’s end user comes from the integration’s End-User Email Expression. Workloads that pass that check act upstream as the resolved person. Only map claims the workload can’t choose itself, such as a verified sub or email claim. Avoid instance tags that anyone with tagging access can set.
If the user hasn’t linked an account, or the link expired, the Connector rejects the request. The error tells the user to link the account on the Resource’s page in the Catalog.

Set up linked accounts (upstream)

1. Choose the OAuth app

Formal needs an OAuth client to sign users in to the upstream’s authorization server.
You don’t register anything. Formal identifies itself in one of two ways:
  • With an app Formal registered with the provider, when there is one.
  • Otherwise, with a Client ID Metadata Document (CIMD), when the authorization server supports it.
Formal checks the authorization server when you save. If neither works, saving fails. Use your own OAuth app instead.
Use this for providers such as GitHub, where you register an app yourself.
  1. Register an OAuth app with the provider.
  2. Add the Redirect URI that Formal shows to the app’s allowed redirect URIs.
  3. Enter the app’s Client ID and, if it has one, its Client secret.
Formal encrypts the secret and never shows it again. Leave the secret empty for a public client, which uses PKCE alone.
Formal doesn’t register itself with authorization servers through Dynamic Client Registration (DCR).

2. Point Formal at the authorization server

Formal discovers the authorization server from the Resource’s protected resource metadata (RFC 9728). MCP servers usually publish it. Most other APIs don’t. For those, set the Authorization server to the provider’s issuer URL. Formal then reads the issuer’s authorization server metadata directly. The issuer must use https, have no query or fragment, and exactly match the issuer its metadata reports. Verify:

3. Choose scopes

Use Permissions to request to list scopes. When you leave it empty:
  • For an MCP server, Formal requests the scopes in its sign-in challenge, or else those its protected resource metadata lists.
  • Otherwise, Formal omits the scope parameter, and the provider’s defaults apply.
You can change the scopes later. New scopes apply the next time each user links. Linked accounts keep the scopes they were granted.

4. Save the settings

  1. Go to Resources and open the Resource.
  2. Open the Authentication tab.
  3. Under Linked Accounts, click Set Up Linked Accounts.
  4. Choose the OAuth app, and set the Authorization server and Permissions to request if needed.
  5. Click Save linked accounts.
Formal adds a linked-account native user that sends each user’s token as a bearer token. It becomes the default native user unless the Resource already has one.You can also click Add User under Native Users and choose Linked Account (OAuth).For MCP servers, configure Authentication on the server’s Configuration tab in the Catalog instead.
You can update the scopes and the client secret in place. Linked accounts and client approvals are kept.
The OAuth app mode, client ID, and authorization server are fixed. Changing one deletes and recreates the settings. That unlinks every account and revokes every client approval for the Resource.
Verify: Open the Resource in the Catalog as a user, and go to the Installation tab. You’ll see a Your account panel with a Link account button.

5. Choose how the token is sent

Use linked_http_basic for upstreams that only accept Basic credentials, such as git over HTTPS:
The default selection can mix linked accounts with shared credentials:
Clients can also request any of the Resource’s native users by label with @<label>. That includes one with a shared credential in place of their linked account. To restrict that, write a policy. Users link their own accounts:
  1. Open the Resource in the Catalog, and go to the Installation tab.
  2. Under Your account, click Link account, and sign in to the provider.
Users can also link during Formal OAuth sign-in, on the consent screen. Formal stores tokens encrypted in its control plane. When the provider issues refresh tokens, Formal refreshes access tokens before they expire. Otherwise, users link again once the access token expires. Verify: Send a request through the Connector as that user. For example, through the Formal Endpoint:

Formal OAuth (downstream)

With OAuth enabled, the Connector is an OAuth protected resource for the Resource. Clients that support MCP-style authorization sign in with Formal:
  1. The client calls the Resource’s Connector address without a token.
  2. The Connector answers 401 Unauthorized. Its WWW-Authenticate header points to /.well-known/oauth-protected-resource, which lists Formal as the authorization server.
  3. The user signs in to Formal and approves the client on a Catalog consent screen. If the Resource uses linked accounts, the user links there too.
  4. Formal issues an access token bound to this Resource’s address, plus a refresh token.
The Connector address uses smart routing:
The Connector validates access tokens offline and removes them before forwarding the request.
Formal OAuth works when upstream authentication is linked accounts or shared credentials. Requests that identify no Formal user are rejected.

Clients that can sign in

Every client must use PKCE with S256. Formal’s authorization server doesn’t support Dynamic Client Registration (DCR). Clients identify themselves in one of two ways: To pre-register a client:
  1. Go to OAuth and open the Clients tab.
  2. Create a client with its name and exact redirect URIs.
  3. Make it confidential if the client can keep a secret. Copy the secret now; Formal shows it only once.
  4. Configure the client with the client ID and, if confidential, the secret.
Redirect URIs must be https, http on a loopback host (any port), or a private-use scheme containing a period. Confidential clients authenticate with client_secret_basic or client_secret_post. Public clients use PKCE alone.

Manage approvals and linked accounts

Users manage their own:
  • Authorized apps in the Catalog account menu lists every client they approved, with a revoke action.
  • Each Resource’s Installation tab in the Catalog shows their linked account, with Unlink.
Administrators manage the whole organization on the OAuth page: Revoking an approval stops the client from refreshing. Access tokens it already holds keep working on Connector Resources until they expire, within 15 minutes. Unlinking revokes the tokens at the provider when it supports revocation, then deletes them. If the provider doesn’t confirm, revoke the authorization in the provider’s settings too.

Policies and logs

Requests authenticated with a Formal OAuth access token carry input.user.oauth: the client’s ID, name, type, and the consent ID. Logs record the same fields under user.formal.oauth. A cimd client chooses its own name, so match on input.user.oauth.client.id or type. See OAuth clients for every field and an example that allows only pre-registered clients.

Troubleshooting

Cause: The user the request is for has no linked account, or its access token expired without a refresh token.Fix: Link or re-link the account on the Resource’s page in the Catalog. For OIDC federation tokens, check these too:
  • The federation token names an end user. The mint response includes end_user_id.
  • That end user, not the machine user, linked the account.
  • The Connector runs 2.25.2 or later.
Cause: The Resource has linked accounts but no default native user, and the request didn’t name one with @<label>.Fix: Make the linked-account native user the default. With Terraform, add a formal_resource_native_user_selection that picks it.
Cause: The default native user, or the one the client requested with @<label>, sends a shared credential.Fix: Make the linked-account native user the default. To stop clients requesting another one, write a policy.
Cause: The upstream refused the linked account’s token, for example after a password change or revoked grant. The native user may also send it in a form the upstream doesn’t accept.Fix: Re-link the account. If the token lacks a permission, add the scope under Permissions to request, then re-link. For git over HTTPS, use linked_http_basic and Connector 2.25.14 or later. Older Connectors always send the token as a bearer token.
Possible causes:
  • The Resource publishes no protected resource metadata, and no authorization server is set
  • The authorization server supports neither CIMD nor an app Formal registered
Fix: Set the Authorization server to the provider’s issuer. If saving still fails, switch to Your OAuth App.
Cause: The client doesn’t support Client ID Metadata Documents, or its redirect URI isn’t registered.Fix: Pre-register the client with its exact redirect URIs.

Next Steps

MCP Gateway

Route remote MCP servers through a Connector

OIDC

Give workloads federated Connector access

Native Users

Send shared credentials instead

Policy Evaluation

Write policies on OAuth clients