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.
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.Formal's app (formal)
Formal's app (formal)
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.
Your OAuth app (pre_registered)
Your OAuth app (pre_registered)
Use this for providers such as GitHub, where you register an app yourself.
- Register an OAuth app with the provider.
- Add the Redirect URI that Formal shows to the app’s allowed redirect URIs.
- Enter the app’s Client ID and, if it has one, its Client secret.
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.
4. Save the settings
- Control Plane
- Terraform
- Go to Resources and open the Resource.
- Open the Authentication tab.
- Under Linked Accounts, click Set Up Linked Accounts.
- Choose the OAuth app, and set the Authorization server and Permissions to request if needed.
- Click Save linked accounts.
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.5. Choose how the token is sent
Use
linked_http_basic for upstreams that only accept Basic credentials, such as git over HTTPS:
@<label>. That includes one with a shared credential in place of their linked account. To restrict that, write a policy.
Link an account
Users link their own accounts:- Open the Resource in the Catalog, and go to the Installation tab.
- Under Your account, click Link account, and sign in to the provider.
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:- The client calls the Resource’s Connector address without a token.
- The Connector answers
401 Unauthorized. ItsWWW-Authenticateheader points to/.well-known/oauth-protected-resource, which lists Formal as the authorization server. - 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.
- Formal issues an access token bound to this Resource’s address, plus a refresh token.
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 withS256. Formal’s authorization server doesn’t support Dynamic Client Registration (DCR). Clients identify themselves in one of two ways:
To pre-register a client:
- Control Plane
- Terraform
- Go to OAuth and open the Clients tab.
- Create a client with its name and exact redirect URIs.
- Make it confidential if the client can keep a secret. Copy the secret now; Formal shows it only once.
- Configure the client with the client ID and, if confidential, the secret.
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.
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 carryinput.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
"Formal has no current <resource> sign-in for you"
"Formal has no current <resource> sign-in for you"
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.
"No default native user is configured for <resource>"
"No default native user is configured for <resource>"
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.Requests reach the upstream without the user's token
Requests reach the upstream without the user's token
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."<resource> rejected your credentials"
"<resource> rejected your credentials"
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.Saving linked accounts fails
Saving linked accounts fails
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
Invalid client error during Formal OAuth sign-in
Invalid client error during Formal OAuth sign-in
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