The design I got approved was impossible, and the discovery took one call
- Symptom
The plan was clean and reviewed: a thin proxy that authenticates to the vendor’s own hosted tool endpoint with an organisation credential and forwards filtered read-only calls. Wiring it up produced an error at the authorisation step, and then a second error on refresh.
- Diagnosis
Two independent problems, and the second one killed the design.
The backend’s generic OAuth support is hard-wired to authorisation code with PKCE and a refresh token. A machine-to-machine credential has neither an interactive authorisation step nor a refresh token, so it failed at the first and then failed again when the platform tried to refresh something that does not exist. That was fixable in principle.
The decisive finding was one step earlier, and I only got it by testing the credential directly against the vendor rather than through the platform. The credential is valid: it mints a token successfully. The vendor’s hosted tool endpoint then rejects that token because it carries no user identity. The identical token works against the vendor’s ordinary REST API. The endpoint I had designed the proxy around structurally cannot accept the only credential type the vendor would give us for our own organisation.
- Decision
Keep every goal, change the transport. The proxy self-authenticates with the organisation credential and calls the REST API instead, with a client that issues read requests only, which turns “read-only” from a filtering promise into a property of the client. Twelve allowlisted tools map onto read endpoints. No backend changes were needed, no per-user OAuth was needed, and the vendor’s one-token-per-application rule is respected by a single process-wide token that re-mints shortly before expiry.
The token manager and the guardrail layer survived the rewrite unchanged. What was thrown away was the assumption that the vendor’s own tool endpoint was the right thing to talk to.
platform oauth expects authorisation code + PKCE + refresh machine credential has neither → error at authorise, 400 on refresh token mint ok ← the credential itself is valid hosted tool endpoint 401 “token missing user identity” same token → REST API 200 verdict the designed transport cannot accept this credential type
proxy self-authenticates, caches one token process-wide, remints shortly before the 1h expiry, no refresh token client read requests only → read-only by construction tools 12, allowlisted, mapped to read endpoints backend changes none guardrail layer unchanged from the original plan
The lesson is about the order I tested things in. I validated the credential through our own platform first, which produced a confusing platform-shaped error and sent me looking at our OAuth code. One direct call to the vendor, made first, would have told me the design was dead before it was written. Test the assumption furthest outside your control before the code that depends on it.