OAuth 2.1 with PKCE brings secure auth to remote MCP servers
Eugen's Dev.to post walks through replacing static API keys on a remote MCP server with a full OAuth 2.1 + PKCE browser-based consent flow that supports multi-tenant team selection and short-lived tokens.
Score breakdown
Developers building multi-tenant SaaS products on MCP can use this pattern — OAuth 2.1 + PKCE with per-team scoping — to ship user-facing AI integrations without exposing static API keys or building custom auth from scratch.
- 01The remote MCP server runs at `https://mcp.paperlink.online/api/mcp/mcp` using streamable HTTP transport — no local process or binary required.
- 02Clients connect with `claude mcp add --transport http paperlink https://mcp.paperlink.online/api/mcp/mcp`.
- 03Auth uses OAuth 2.1 authorization code flow with PKCE, replacing static API keys.
Eugen's post tackles what he calls the hardest part of building a production MCP integration: secure authentication for a remote, multi-tenant SaaS server. The standard local MCP setup — a JSON config block with a static `API_KEY` environment variable — fails for SaaS products on three counts: API keys are static secrets with no expiry or scope, there is no user consent flow, and there is no way to handle multi-tenancy where a user belongs to multiple teams.
On first connection, the client discovers OAuth endpoints through two well-known documents, then runs a standard OAuth 2.1 authorization code flow with PKCE.
The solution is a remote MCP server using streamable HTTP transport (no local process, no stdio, no binary to install), connected via `claude mcp add --transport http paperlink https://mcp.paperlink.online/api/mcp/mcp`. On first connection, the client discovers OAuth endpoints through two well-known documents, then runs a standard OAuth 2.1 authorization code flow with PKCE. The user sees a browser consent screen where they select a team and review requested scopes displayed with i18n-sourced human-readable labels — `invoices:read` appears as "View invoices and estimates" and `accounting:write` as "Create and modify transactions." Each MCP connection is intentionally scoped to exactly one team, so users who want AI access to multiple teams create separate connections with independently configured permissions.
On approval, a server action generates a 256-bit authorization code (two concatenated UUIDs) stored as a domain entity — not a raw database record — with a 10-minute expiry set automatically. The PKCE challenge is stored alongside for verification. Invalid scopes are silently dropped rather than rejected, preventing a misbehaving client from blocking the flow. The resulting access token is short-lived, and a 30-day refresh token handles renewal. Users can revoke access at any time from the app's settings page.
Key facts
- 01The remote MCP server runs at `https://mcp.paperlink.online/api/mcp/mcp` using streamable HTTP transport — no local process or binary required.
- 02Clients connect with `claude mcp add --transport http paperlink https://mcp.paperlink.online/api/mcp/mcp`.
- 03Auth uses OAuth 2.1 authorization code flow with PKCE, replacing static API keys.
- 04Users receive a short-lived access token and a 30-day refresh token after browser-based consent.
- 05Each MCP connection is scoped to exactly one team; users wanting multi-team AI access create separate connections.