Forma

What it took to make paste-one-URL AI connections work

The engineering story behind Forma’s OAuth-powered remote MCP connector: discovery, PKCE, token rotation, race conditions, CORS, and consent.

“Paste this URL into Grok” sounds like a small feature.

The URL was the easy part.

Forma already had an Agent API, a local MCP server, scoped Bearer tokens, and a remote MCP endpoint. A developer could connect a tool. The problem was what happened when a normal user tried.

Grok asked for a client ID, authorization endpoint, token endpoint, scopes, and more. The CMS was technically connectable and practically unfinished.

The real requirement became:

A compatible AI client should be able to start with only the MCP URL, discover how authorization works, ask the site owner for consent, and manage credentials without showing them.

That required an authorization server inside a portable PHP and SQLite CMS.

MCP does not solve authorization by itself

MCP describes how an AI client discovers and calls tools. It does not mean every remote server shares one login system.

The first unauthenticated request to Forma's MCP endpoint now returns a Bearer challenge with a pointer to protected-resource metadata. That metadata says which authorization server protects the resource and which scopes are available.

The client then reads authorization-server metadata to discover:

  • The authorization endpoint
  • The token endpoint
  • The dynamic client registration endpoint
  • Supported grants
  • Supported scopes
  • The required PKCE method

This is what turns “a pile of OAuth fields” into “paste one URL.”

Dynamic registration, because the owner should not preconfigure Grok

Forma cannot ship with a hard-coded client ID for every AI product, and a self-hosted install should not require AltaForma to broker every connection.

Dynamic Client Registration lets the tool introduce itself directly to the individual Forma site. It supplies its name and callback URLs; Forma validates those values and returns a public client ID.

Registration is rate-limited and capped. Callback URLs must use HTTPS, except for loopback addresses used by local clients. Fragments, embedded credentials, malformed hosts, and unregistered redirect targets are rejected.

The client is public. There is no client secret pretending to be secret inside a desktop or browser application.

Authorization Code plus PKCE

The connector creates a PKCE verifier and sends its S256 challenge with the authorization request.

Forma validates the client, exact callback URL, requested scopes, resource indicator, response type, and challenge. If the owner is not signed in, Forma saves the validated request in the session, sends the owner through the normal admin login, then resumes at the consent screen.

Approval produces a random, one-time authorization code stored only as a SHA-256 hash. The code expires after five minutes.

The token endpoint accepts the code only when:

  • The client matches
  • The callback URL matches
  • The code is unused and unexpired
  • The PKCE verifier recreates the original challenge
  • The requested resource matches

The database update that marks a code used is conditional. The code exchange succeeds only if exactly one row changes. Two simultaneous requests cannot both spend the same code.

Token rotation exposed a less obvious race

The first refresh-token implementation checked whether a token was unused, issued replacements, and then revoked the old token.

That ordering looks reasonable and is wrong under concurrency.

Two requests could read the old token before either marked it used. Both could issue a valid replacement, forking one grant into two token chains.

The fix was to claim the refresh token first inside the transaction:

UPDATE oauth_refresh_tokens
SET revoked_at = ?
WHERE token_hash = ? AND revoked_at IS NULL

Only the request that changes one row may continue. A later replay triggers connection-wide revocation.

This is a useful general rule: checking a credential and consuming it must be one atomic operation, not two polite suggestions separated by application code.

Audience binding changed existing authentication

Forma's manual API keys work across the scopes granted to them. OAuth access tokens needed a narrower contract: this token was issued for this site's MCP resource.

The existing token table gained expiry, audience, and OAuth client columns. Authentication now checks time and audience in addition to the token hash and scopes.

The MCP route passes its canonical resource URI as the expected audience. An OAuth token presented elsewhere fails closed.

Canonical URLs caused their own test failure. A built-in PHP test server resolved base paths differently from the real vhost, so protected-resource metadata initially described the wrong audience. The integration harness had to set document-root and script-name context explicitly. URL code is infrastructure code; test environments do not get to wave that away.

Browser details became protocol details

Several bugs lived outside the token algorithms:

  • OAuth endpoints needed their own HTTPS enforcement. Protecting only the API route left registration and token exchange exposed.
  • Login needed CSRF verification before it could safely become part of an authorization flow.
  • A pending authorization request had to survive login without turning arbitrary return URLs into an open redirect.
  • MCP could not keep a wildcard CORS policy once it carried owner-authorized credentials.
  • Authorization responses added the issuer parameter so clients could bind the response to the server that produced it.
  • WWW-Authenticate had to advertise both resource metadata and the available scope set.

Interoperability lives in these details. A standards-shaped feature with missing metadata is still a feature users cannot connect.

Consent is part of the product

The first consent page worked and looked like a debug screen: borrowed login styles, default list bullets, a destructive red Cancel button, and mismatched spacing.

That was not cosmetic trivia. This page asks an owner to make a security decision. It should clearly answer:

  • Which tool is asking?
  • Which site is it asking to edit?
  • What can it do?
  • What can it not do?
  • What are the two decisions?

The finished page uses dedicated consent markup and CSS, green checks for granted capabilities, a neutral Cancel action, a primary Allow action, mobile stacking, and cache-busted styles. Security UI still has to be UI.

SQLite was enough

The implementation added three small tables:

  • Registered OAuth clients
  • One-time authorization codes
  • Rotating refresh tokens

SQLite transactions handle code consumption and refresh rotation. Random credentials are hashed before storage. Expired records are pruned. Revocation updates the client and its credentials together.

No Redis cluster, external identity provider, or background worker was necessary.

The scale of a single-site CMS is an advantage when the design respects it.

What “done” looked like

The automated test covers:

  • Discovery metadata
  • Dynamic registration
  • Redirect and scope rejection
  • PKCE authorization and exchange
  • One-time code replay defense
  • Audience-bound API authentication
  • Refresh rotation and reuse response
  • Client revocation
  • HTTPS enforcement

Then came the test that mattered: a fresh Grok connector received only https://forma-cms.me/api/v1/mcp, opened the Forma consent screen, and connected successfully without the user typing a token or any advanced OAuth field.

The final interface was one URL. Everything else existed so the user did not have to care.