May 21, 2026
Service API Keys vs OAuth: Auth Architecture for Agentic Platforms
When AI agents call APIs, they need authentication. OAuth browser flow does not work for stdio MCP servers. Three auth paths — when to use which.
In the B2B SaaS stack of the last ten years, authentication was a solved problem. Browser session cookies for user login, OAuth for third-party integrations, JWT tokens for service-to-service. Three patterns, cleanly separated, each with its use case. Then came MCP.
MCP servers introduce a new class of clients: AI agents that call tools at runtime, often from a stdio context, with no browser and no stable user login. That breaks the established auth topology. Over the last six months we have built several MCP servers and kept hitting the same finding: none of the three classical auth paths runs one-to-one, all three need adjustments, and the choice of path determines adoption more than tool quality does.
Path one: Clerk session cookies (browser only)
Clerk session cookies are what our web UIs use. A user logs in, gets a cookie, the cookie travels with every request, the server verifies it against Clerk. This works perfectly for browser contexts and is the right path for most web frontend use cases.
The moment there is no browser involved — for example when Claude Desktop spawns a stdio MCP server — there is no cookie to send. No browser redirect, no session, no cookie jar. The MCP server would have to keep a separate browser session open to fetch cookies, which makes complexity explode and cannot be cleanly solved in an unattended background process anyway. Clerk session cookies are therefore not a candidate for MCP servers.
Path two: JWT tokens (short-lived, refresh cycle)
JWT tokens are the first attempt to work around the cookie problem. Instead of a browser cookie, the client gets a signed token that it sends in the `Authorization` header. That works without a browser — the client just has to obtain the token somehow.
That is exactly where the problem sits. JWT tokens are short-lived by design (typically 15 minutes to one hour) and need a refresh cycle. The refresh cycle either needs a browser (OAuth standard flow) or a separate refresh-token mechanism, which itself needs to be stored securely. In an MCP server context that means: the server either requests a new token at every tool call (performance killer) or holds refresh tokens on disk (security risk). Both are unsatisfying.
JWT tokens make sense when token issuance runs over a trusted identity provider that keeps refresh tokens safely — typically in server-to-server communication inside the same infrastructure. For the MCP server use case, where the client is a user desktop, they need a wrapping layer that hides the refresh problem.
Path three: service API keys (long-lived, single-purpose)
Service API keys are the pragmatic answer. A service user is defined server-side, gets a long-lived key assigned (typically six to twelve months validity), the key is configured in the MCP server config as a bearer token and sent with every tool call in the `Authorization` header.
Pros: no refresh cycle, no browser, no separate user session. The tokens are single-purpose (bound to a service user, not a human user) and therefore explicitly not suitable for cross-application SSO — which is often exactly what B2B contexts need. Cons: long token lifetime increases blast radius on leaks, and you need a UI workflow for token rotation.
We chose this path for the MMM Wizard MCP server — and describe the concrete implementation here.
Implementation: proxy.ts with header injection
The MMM Wizard has a `proxy.ts` (Next.js 16 middleware equivalent) that inspects every incoming request. The workflow:
Step 1: extract the `Authorization` header. If it starts with `Bearer `, the token is isolated.
Step 2: verify the token against the service-API-keys table in the Postgres database. The table holds `id`, `service_user_id`, `hashed_token`, `expires_at`, `revoked_at`. The verification check is a hash lookup with constant-time compare against timing attacks.
Step 3: if the token is valid, inject `service_user_id` as `x-mmm-service-user` header into the backend request. Backend routes read this header and treat the request as if it came from the corresponding service user.
Step 4: audit log entry with token ID, service user ID, tool name, timestamp. Every tool call is traceable.
The pattern is deliberately simple — no OAuth dance, no refresh logic, no external identity-provider round trip. The trade-off is explicit: longer token lifetimes against simpler implementation and faster tool calls. In a B2B context with controlled service identities, that trade-off is defensible.
Sprint 5 outlook: /admin/api-keys UI with rotation
Missing so far is the UI for token management. In the current state, tokens are created manually via an internal script — which works for the first customer implementations but does not scale. Sprint 5 (scheduled for June 2026) ships an `/admin/api-keys` page with:
- Token creation with service-user selection and expiry-date picker.
- Token shown exactly once on creation (afterwards only the hash is stored).
- Rotation workflow: old token stays valid in parallel for 7 days, then revoked.
- Audit trail per token with tool-call histogram and last usage.
- Notification to the service-user owner on rotation and 30 days before expiry.
This is not rocket science, but it is the threshold at which the auth system becomes production-ready. Without a rotation UI, long-lived tokens are a liability — with a rotation UI they are a pragmatic solution to a concrete problem.
Which path when — the decision matrix
Browser frontend with user login: Clerk session cookies. Period. Anything else is over-engineering.
Server-to-server inside the same infrastructure, with identity provider: JWT tokens. That is the standard path for microservice communication in modern stacks.
MCP servers, mobile app backends, CLI tools, cron jobs, any context without a browser session: service API keys with a rotation UI. That is the path most B2B teams arrive at as soon as they start integrating AI agents into their stack seriously.
The most important insight from six months of MCP server building: the auth choice is the single most important architecture decision for an MCP server. Pick wrong and you either block your own adoption (OAuth browser flow in stdio context) or build complexity that can never be reduced (JWT refresh tokens without a clean refresh path).
Open-source pattern on GitHub
The full auth pattern — proxy.ts, service-API-keys table, header-injection middleware, audit logging — is part of the MMM Wizard repo on GitHub: github.com/Baldri/mmm-wizard. MIT-licensed, copy-paste ready for your own MCP server projects. Questions on adaption: find us at digital-opua.ch.