← Back

prodOAuth + PKCE

Letting an AI into customer data, carefully

Claude querying customer data securely, in production

security · AI agents

Context

Zipy wanted customers to be able to ask Claude questions about their own session-replay and error data instead of navigating a dashboard. The Model Context Protocol is how an AI client connects to external tools, so that meant a remote MCP server exposing Zipy's data.

The problem

An AI agent reading a customer's production data on their behalf is security-critical. It needed per-customer authorization, per-plan access control, and it had to work with clients we didn't know in advance. There was no internal prior art.

What I did

I built a remote MCP server exposing tools like get_session, search_errors and get_user_journey, secured with full OAuth 2.0:

  • PKCE
  • Dynamic client registration (RFC 7591)
  • Authorization server metadata discovery (RFC 8414)
  • Plan-based tool access control

Decisions

OAuth, not API keys. API keys are bearer secrets with no scoping, expiry or per-user revocation, and they end up in config files. OAuth gives consent, scoped tokens, expiry and revocation — what you need when something acts on a user's behalf.

PKCE, because MCP clients are public. They can't safely hold a client secret. The client sends a hash first and reveals the secret only at the end, so an intercepted authorization code is useless:

1. client generates code_verifier  (random, 43–128 chars)
2. code_challenge = BASE64URL(SHA256(code_verifier))
3. /authorize  with code_challenge, method=S256
4. server stores challenge (short TTL), returns auth_code
5. /token      with auth_code + code_verifier
6. server checks SHA256(verifier) == stored challenge
7. access_token + refresh_token

Dynamic registration and discovery. You don't know MCP clients ahead of time, so hand-registering client IDs doesn't scale. Discovery plus registration lets clients configure themselves — which makes exact redirect-URI matching and rate limiting on registration load-bearing.

Enforce on every call. Scopes derive from the customer's plan at token issuance but are enforced at tool invocation, not just at connection. Otherwise a long-lived token outlives a plan downgrade.

What it cost

Significant protocol complexity for a small team — several RFCs, token lifecycle management, and a registration endpoint open to unknown clients, which is itself attack surface to defend.

Outcome

Claude could securely query a customer's Zipy session and error data, scoped to what their plan allowed, in production.

What I learned

Designing for AI agents as first-class API consumers is a different problem from designing for people. Auth, discovery and access control all have to work with nobody at a consent screen at call time. And the model's intent is never the authorization.