feat(content): 11 new SEO/GEO guide articles with per-article OG images
How-to cluster: create-mcp-server-without-code, claude-desktop-mcp-setup, chatgpt-mcp-connector (incl. honest Business/Enterprise write-connector plan limits), rest-api-to-mcp-server. Comparison cluster: mcp-server-hosting-pricing (5-platform matrix), composio-alternative, smithery-alternative — house style: explicitly state where competitors win. Technical/GEO cluster: mcp-transports-explained (SSE deprecation 2025-03-26), mcp-oauth-plain-english, mcp-server-security-checklist, and German DACH article mcp-server-ohne-code-erstellen (articleJsonLd gained optional inLanguage param). Every article: pageMetadata canonical, Article JSON-LD with Person author + image + wordCount, BreadcrumbList, FAQPage where applicable, 1200x630 OG image via shared helper, internal linking. Registry entries in lib/articles.ts feed guides index, sitemap and RSS automatically. Product claims sourced only from lib/seo.ts truth — no invented customers, SLAs or certifications. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SXUwmPVRTD8AKQtio6gCN5
This commit is contained in:
180
apps/web/app/(marketing)/guides/mcp-oauth-plain-english/page.tsx
Normal file
180
apps/web/app/(marketing)/guides/mcp-oauth-plain-english/page.tsx
Normal file
@@ -0,0 +1,180 @@
|
||||
import { JsonLd } from '@/components/json-ld';
|
||||
import { articleJsonLd, breadcrumbJsonLd, pageMetadata } from '@/lib/seo';
|
||||
import Link from 'next/link';
|
||||
import { ArticleShell, H2, Note, OL, P, Strong, UL } from '../article-shell';
|
||||
|
||||
const PATH = '/guides/mcp-oauth-plain-english';
|
||||
const TITLE = 'OAuth 2.1 for MCP servers: PKCE, DCR and RFC 8707 in plain English';
|
||||
const DESCRIPTION =
|
||||
'The three RFCs behind MCP authorization — PKCE, Dynamic Client Registration and Resource Indicators — explained without jargon: what each one does, the full flow step by step, and what breaks when you skip one.';
|
||||
|
||||
export const metadata = pageMetadata({ title: TITLE, description: DESCRIPTION, path: PATH });
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<JsonLd
|
||||
data={articleJsonLd({
|
||||
title: TITLE,
|
||||
description: DESCRIPTION,
|
||||
path: PATH,
|
||||
datePublished: '2026-07-08',
|
||||
authorName: 'Marco Sadjadi',
|
||||
wordCount: 1400,
|
||||
})}
|
||||
/>
|
||||
<JsonLd
|
||||
data={breadcrumbJsonLd([
|
||||
{ name: 'Home', path: '/' },
|
||||
{ name: 'Guides', path: '/guides' },
|
||||
{ name: TITLE, path: PATH },
|
||||
])}
|
||||
/>
|
||||
<ArticleShell
|
||||
title={TITLE}
|
||||
subtitle="MCP authorization is OAuth 2.1 plus three RFCs that most OAuth tutorials never mention. This is the concept explainer — what each piece does and why the spec requires it. For the deployment how-to, see the hosting guide."
|
||||
updated="July 2026"
|
||||
>
|
||||
<H2>Why MCP needs more than classic OAuth</H2>
|
||||
<P>
|
||||
Classic OAuth assumes you know your clients in advance: you register an app in a
|
||||
dashboard, get a <code>client_id</code> and <code>client_secret</code>, and ship them
|
||||
inside your application. MCP breaks both assumptions. The client is someone's Claude
|
||||
Desktop or Cursor install — you will never pre-register it — and it runs on a desktop
|
||||
where a baked-in secret is not a secret. OAuth 2.1 plus three extension RFCs is how the
|
||||
MCP spec resolves this.
|
||||
</P>
|
||||
|
||||
<H2>The two roles: Authorization Server and Resource Server</H2>
|
||||
<P>
|
||||
Every OAuth setup splits into two jobs. The <Strong>Authorization Server (AS)</Strong>{' '}
|
||||
authenticates users and issues tokens. The <Strong>Resource Server (RS)</Strong> — your
|
||||
MCP server — accepts requests only when they carry a valid token. They can be the same
|
||||
deployment or separate services; the protocol only cares that the RS can verify what the
|
||||
AS signs, typically via a published JWKS (the AS's public keys).
|
||||
</P>
|
||||
|
||||
<H2>PKCE (RFC 7636): proof that the token requester started the flow</H2>
|
||||
<P>
|
||||
The authorization-code flow hands the client a one-time code via a browser redirect, and
|
||||
the client exchanges that code for a token. The classic attack is stealing the code
|
||||
in-flight and exchanging it yourself. <Strong>PKCE</Strong> closes this: the client
|
||||
invents a random secret (the verifier), sends only its hash (the challenge) when the flow
|
||||
starts, and must present the original verifier at exchange time. A thief who intercepted
|
||||
the code never saw the verifier, so the code is useless to them.
|
||||
</P>
|
||||
<P>
|
||||
In OAuth 2.1, PKCE is <Strong>mandatory for every client</Strong> — the old exemption for
|
||||
"confidential" server-side clients is gone. If your AS treats PKCE as optional, it is not
|
||||
OAuth 2.1.
|
||||
</P>
|
||||
|
||||
<H2>Dynamic Client Registration (RFC 7591): clients register themselves</H2>
|
||||
<P>
|
||||
When a user adds your MCP server to Claude Desktop, the client calls your AS's
|
||||
registration endpoint at runtime — "here is my name and redirect URI" — and receives a
|
||||
fresh <code>client_id</code> on the spot. No dashboard, no support ticket, no shared
|
||||
credentials between users. <Strong>DCR</Strong> is what makes "paste a URL, connect,
|
||||
done" possible: without it, every user of every MCP client would need you to manually
|
||||
provision app credentials.
|
||||
</P>
|
||||
|
||||
<H2>Resource Indicators (RFC 8707): tokens bound to one server</H2>
|
||||
<P>
|
||||
A user might connect their client to ten different MCP servers. Without{' '}
|
||||
<Strong>Resource Indicators</Strong>, a token minted for server A could be replayed
|
||||
against server B — any server you talk to could impersonate you elsewhere. RFC 8707 has
|
||||
the client name the exact server (the <code>resource</code>) when requesting the token,
|
||||
and the AS bakes that audience into the token. Your MCP server then rejects any token
|
||||
whose audience is not itself. One token, one server, no cross-server replay.
|
||||
</P>
|
||||
|
||||
<H2>The full flow, step by step</H2>
|
||||
<OL>
|
||||
<li>
|
||||
The client sends an unauthenticated request to your MCP server and gets a{' '}
|
||||
<Strong>401</Strong> with a <code>WWW-Authenticate</code> header pointing at the
|
||||
protected-resource metadata — this is how the client discovers your AS.
|
||||
</li>
|
||||
<li>The client registers itself with the AS via DCR and receives a client_id.</li>
|
||||
<li>
|
||||
The client generates a PKCE verifier + challenge and opens the browser to the AS's
|
||||
authorize endpoint, naming your server as the <code>resource</code>.
|
||||
</li>
|
||||
<li>The user signs in and approves; the AS redirects back with a one-time code.</li>
|
||||
<li>
|
||||
The client exchanges code + PKCE verifier for an access token that is
|
||||
audience-bound to your server.
|
||||
</li>
|
||||
<li>
|
||||
Every subsequent MCP request carries the token; your server verifies signature, expiry
|
||||
and audience on each call.
|
||||
</li>
|
||||
</OL>
|
||||
|
||||
<H2>What breaks when you skip a piece</H2>
|
||||
<UL>
|
||||
<li>
|
||||
<Strong>No protected-resource metadata / wrong 401</Strong> — clients can't discover
|
||||
your AS. The symptom: the server "works" with curl but silently fails to install from
|
||||
Claude Desktop. This is the most common failure in the wild.
|
||||
</li>
|
||||
<li>
|
||||
<Strong>No DCR</Strong> — the connect flow dead-ends at "unknown client" for anyone but
|
||||
you.
|
||||
</li>
|
||||
<li>
|
||||
<Strong>No PKCE (or PKCE accepted but not enforced)</Strong> — the flow appears to work
|
||||
but intercepted authorization codes become exchangeable. Invisible until exploited.
|
||||
</li>
|
||||
<li>
|
||||
<Strong>No audience check (RFC 8707)</Strong> — tokens for other servers are accepted
|
||||
by yours, and yours are accepted elsewhere. Also invisible until exploited.
|
||||
</li>
|
||||
</UL>
|
||||
<Note>
|
||||
The failure modes split into two families: discovery mistakes are loud (nothing
|
||||
connects), security mistakes are silent (everything connects, including attackers). Test
|
||||
for both — an end-to-end install from a real client proves discovery, but only negative
|
||||
tests (expired token, wrong audience, missing PKCE) prove the security half.
|
||||
</Note>
|
||||
|
||||
<H2>Common implementation mistakes</H2>
|
||||
<UL>
|
||||
<li>Substring-matching redirect URIs instead of exact-match comparison.</li>
|
||||
<li>Allowing authorization codes to be exchanged more than once.</li>
|
||||
<li>Accepting <code>plain</code> PKCE instead of requiring <code>S256</code>.</li>
|
||||
<li>
|
||||
Verifying token signature and expiry but forgetting the audience claim — RFC 8707 only
|
||||
protects you if the RS actually checks it.
|
||||
</li>
|
||||
<li>
|
||||
Long-lived access tokens as a substitute for refresh tokens — shorter access-token
|
||||
lifetime plus refresh is the OAuth 2.1 posture.
|
||||
</li>
|
||||
</UL>
|
||||
|
||||
<H2>Build it or get it built-in</H2>
|
||||
<P>
|
||||
None of this is exotic, but it is a genuine authorization-server implementation — days of
|
||||
work plus ongoing spec-tracking, and mistakes are security bugs rather than build
|
||||
failures. If you want to own it, the{' '}
|
||||
<Link
|
||||
href="/guides/host-mcp-server-with-oauth"
|
||||
className="text-[--color-accent] hover:underline"
|
||||
>
|
||||
hosting guide
|
||||
</Link>{' '}
|
||||
walks through the deployment options. If you'd rather not: every server generated on
|
||||
BuildMyMCPServer ships behind an OAuth 2.1 AS with PKCE, DCR and Resource Indicators
|
||||
already wired — the flow above is what our{' '}
|
||||
<Link href="/docs/oauth" className="text-[--color-accent] hover:underline">
|
||||
OAuth docs
|
||||
</Link>{' '}
|
||||
implement, and the <Link href="/pricing" className="text-[--color-accent] hover:underline">free tier</Link>{' '}
|
||||
includes it.
|
||||
</P>
|
||||
</ArticleShell>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user