OAuth assumes your user already has an account

You want an agent to set up a third-party service for a user in the middle of a conversation. Create the project, configure it, hand over working credentials. The obvious answer is OAuth. Then you hit the assumption buried in the authorization-code flow: the user has to authenticate with the provider, which means the user already has an account there. Any flow whose value proposition is “try this service without signing up first” cannot start with OAuth. There is no account to authorize against.

I ran into this at ZeroClick, leading a system that let an agent set up a third-party service for a user mid-conversation. We demoed it to partners and advertisers, and it shaped the company’s subsequent product direction, though it never shipped as a standalone product in that form. The patterns below are what survived contact with real providers, real load balancers, and real users who open emails hours late.

OAuth is one strategy for obtaining a token

The unlock came from a refactor we shipped as a breaking change. The provisioning path originally assumed OAuth end to end. We changed it to accept an opaque access token and stop caring where the token came from. Behind that one interface, three acquisition strategies became possible: the user completes OAuth, the user pastes a platform API key, or the platform supplies credentials from a pool of accounts it owns. The execution code is identical in all three cases. Which strategy applies is configuration, decided per service.

This sounds small. It changes what you can build. The pooled-account strategy is the one OAuth can never give you: the user tries a fully provisioned service before they have any relationship with the provider at all.

Resource pools are a semaphore over someone else’s quota

Provisioning under your own accounts means living inside the provider’s limits. Many providers cap free projects per account — ours allowed two — so a single platform account can’t serve concurrent users. You need a pool, and the pool is a concurrency problem: select the least-loaded enabled account that is under its cap, increment its counter, and release the slot on teardown. All of that has to happen inside a single database transaction. Without it, two concurrent requests read the same count, both pick the same account, and you oversubscribe a quota you don’t control. The pool’s API keys are encrypted at rest; they are the platform’s own credentials, and a leak burns every user on that account.

The bug worth knowing in advance is on the release path. Ours released the account slot in a code path that ran even when teardown had failed. The slot went back into the pool while the project it represented was still alive, and the counter drifted under the true usage. Release the slot only after teardown succeeds. A failed teardown should hold the slot and alert.

Deferred ownership: the claim pattern

Provisioning under platform credentials creates a resource the user doesn’t own. The claim pattern fixes that: let the user try the service, then transfer ownership when they decide to keep it. Two design points made ours work.

First, the link you send the user is a stable URL you control, with an identifier for the provisioned resource — never the provider’s own transfer link. Second, the provider’s invite or transfer link gets generated at click time, when the user opens yours. Provider transfer links are typically short-lived and single-use. One minted at send time is dead by the time anyone opens the email. The click-time hop earns its keep twice over: it is also where you record that the user engaged and set a session cookie for the steps that follow.

Time-bound everything you provision

Anything created under platform credentials needs a deadline. Ours was a two-hour trial window before automatic teardown — cut down from an original fifteen days once we saw how the economics worked. The stored provisioning context lived slightly longer than the deadline itself, three hours against two, so the cleanup worker had a grace period to find everything it needed even if it ran late.

The failure mode here is quiet: a retry path that re-stores the context. Ours did, on a failed post-claim step, and every retry reset the clock. Resources that should have lived two hours lived indefinitely. Deadlines belong in one place, set once at provisioning time, and no downstream write should touch them.

The OAuth mechanics that bite anyway

For the flows where the user does have an account, OAuth still applies — and agent and multi-tenant contexts hit its sharp edges harder than a classic web app does.

  • The redirect URI is a fixed, per-environment value. Deriving it from the incoming request breaks the moment you’re behind a load balancer — the host the backend sees is the balancer’s, and the derived URI stops matching. Ours broke exactly this way in production. Providers pin exact redirect URIs regardless, so configure it per environment and never compute it.
  • You get one opaque round-trippable field. The state parameter has to carry both a CSRF nonce and where to send the user afterwards — in an agent flow, that return destination is a deep link back into the surface the user came from. Compose a payload with both, encode it, and on the way back validate the return destination strictly. Reject anything that could smuggle extra parameters into the redirect.
  • Store the redirect URI with the state. The token exchange is a different HTTP request from the authorize call, and the spec requires it to present an identical redirect_uri. If the value isn’t stored alongside the state, the exchange has to re-derive it — see the previous item for how that ends.
  • Consume state atomically. Get-and-delete in one operation, so state is genuinely single-use. A read followed by a separate delete leaves a window where a replayed callback succeeds twice.
  • Exchange server-side, always. Tokens and provisioning instructions must never route through the agent or the browser. Anything that transits the client can be read or tampered with there. The client gets a URL to visit and an opaque status to poll; the credentials stay on the server.

Check for scope theater

A closing audit item. Scopes that are registered in the OAuth configuration but never enforced by any endpoint give the appearance of authorization without the substance. Every token looks scoped; nothing checks the scope. We found this in our own gateway and replaced it with real per-request verification. Grep for where scopes are validated. If the answer is nowhere, the consent screen is describing permissions that don’t exist.

The shape that works

Treat token acquisition as a pluggable strategy: OAuth when the account exists, an API key when the user can supply one, a pooled platform account when the user has nothing yet. Guard shared quotas with transactional pools. Defer ownership with a claim step built on stable links and click-time generation. Put a deadline on everything provisioned under your credentials, and keep the deadline out of reach of retries. The agent never touches a credential. That combination let an agent stand up a working third-party service for a user who had never heard of the provider two minutes earlier — which is the flow OAuth alone cannot start.

Questions clients ask

Can an agent complete an OAuth flow on a user’s behalf?

The redirect and consent screen are designed for a human in a browser, and automating them violates most providers’ terms. The workable pattern is to have the agent initiate the flow and hand the user a URL, then resume once the callback lands server-side. If the user has no account with the provider yet, even that fails — which is when you need a platform-owned pool and a deferred claim step instead.

Why not just use API keys everywhere?

API keys work well when the user already has the service and can paste a key. They fail the same way OAuth does for first-touch flows: no account, no key. Treat both as acquisition strategies behind one interface — the execution path should accept an opaque token and not care how it was obtained.

How do I hand over a resource my platform created for a user?

Provision under your own credentials, then transfer ownership when the user claims it. Send the user a stable link you control, and generate the provider’s invite or transfer link at click time — those links are typically short-lived and single-use, so one generated at send time is dead before the email is opened. The click-time step also gives you a natural place to record engagement and set a session cookie.

Is this safe from a security standpoint?

It can be, with discipline. Exchange tokens server-side only — never route credentials or provisioning instructions through the agent or the browser. Encrypt pooled account keys at rest. Consume OAuth state with an atomic get-and-delete so it is genuinely single-use, and validate the return destination embedded in state so it can’t smuggle parameters. Time-bound anything provisioned under platform credentials and tear it down automatically.

Building flows where agents act on users' behalf?

The agent-readiness audit is $3,000, credited toward follow-on work. I evaluate your codebase and auth architecture through an agent's eyes — including exactly the token acquisition, provisioning, and handover patterns this article covers — and you get a sequenced plan with a fixed quote for the build.

Book a free intro callEmail me insteadI personally reply within one business day. · Currently booking September 2026.