Every MCP server builder hits the same wall: the OAuth spec is brutal.
AuthKit is the other side of that wall.
A single Cloudflare Worker + D1 database that handles the entire MCP OAuth specification -- discovery, registration, consent, tokens -- so your MCP server doesn't have to.
Running in production at authkit.open0p.com, powering OAuth for OpZero.sh.
You want to build an MCP server. Claude, ChatGPT, and other clients need to authenticate users with your server. The spec says: implement OAuth 2.1.
That means RFC 9728 (Protected Resource Metadata), RFC 8414 (Authorization Server Metadata), RFC 7591 (Dynamic Client Registration), PKCE with S256, consent screens, token refresh, token revocation, and multi-tenant support. All before your first tool call works.
We spent weeks fighting this. Then we ripped it out into its own service.
AuthKit is a standalone OAuth authorization server (~600 lines) purpose-built for MCP. Your MCP server points its authorization_servers to AuthKit, and the entire OAuth dance -- registration, consent, tokens -- happens here.
Your MCP server's only job: validate the Bearer token.
// Your MCP server's /.well-known/oauth-protected-resource
{
"resource": "https://your-mcp-server.com/mcp",
"authorization_servers": ["https://your-authkit-instance.com"],
"bearer_methods_supported": ["header"]
}That's the entire integration.
| Spec | What | Status |
|---|---|---|
| RFC 9728 | Protected Resource Metadata | Auto-generated per server |
| RFC 8414 | Authorization Server Metadata | Complete |
| RFC 7591 | Dynamic Client Registration | Complete |
| OAuth 2.1 | Authorization code + PKCE (S256) | Complete |
| -- | Token refresh (30-day TTL) | Complete |
| -- | Token revocation | Complete |
| -- | Consent screen with login/signup | Complete |
| -- | Multi-tenant (multiple MCP servers) | Complete |
Claude / ChatGPT AuthKit (CF Worker + D1) Your MCP Server
| | |
| POST /mcp (no token) | |
|------------------------------------------------------------>|
| 401 + WWW-Authenticate | |
|<------------------------------------------------------------|
| | |
| GET /.well-known/oauth-protected-resource |
|------------------------------------------------------------>|
| { authorization_servers: ["https://authkit..."] } |
|<------------------------------------------------------------|
| | |
| GET /.well-known/oauth-authorization-server |
|-------------------------->| |
| { endpoints... } | |
|<--------------------------| |
| | |
| POST /oauth/register | |
|-------------------------->| |
| { client_id } | |
|<--------------------------| |
| | |
| GET /oauth/authorize | |
|-------------------------->| |
| [consent screen] | |
|<--------------------------| |
| [user approves] | |
|-------------------------->| |
| 302 -> callback?code=xxx| |
|<--------------------------| |
| | |
| POST /oauth/token | |
|-------------------------->| |
| { access_token, ... } | |
|<--------------------------| |
| | |
| POST /mcp (Bearer mat_xxx) |
|------------------------------------------------------------>|
| | GET /oauth/userinfo |
| |<--------------------------|
| | { sub, email, name } |
| |-------------------------->|
| [tools response] | |
|<------------------------------------------------------------|
- Cloudflare account (free tier works)
- Wrangler CLI
git clone https://github.com/OpZero-sh/MCPAuthKit.git
cd MCPAuthKit
npm install
# Create the D1 database
wrangler d1 create mcp-authkit-db
# Update wrangler.toml with your database_id from the output above
# Initialize the schema
wrangler d1 execute mcp-authkit-db --file=schema.sql
# Set your admin secret
wrangler secret put ADMIN_KEY
# -> enter a strong random string
# Deploy
wrangler deployLive at https://mcp-authkit.<your-subdomain>.workers.dev.
curl -X POST https://your-authkit.workers.dev/api/servers \
-H "Authorization: Bearer YOUR_ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "My MCP Server",
"resource_url": "https://my-mcp.com/mcp",
"scopes": ["mcp:tools"]
}'Response:
{
"server_id": "srv_abc123...",
"api_key": "sak_xyz789...",
"prm_url": "https://your-authkit.workers.dev/prm/srv_abc123...",
"message": "Set authorization_servers in your PRM to point to this gateway."
}Point your server's Protected Resource Metadata at your AuthKit instance and validate tokens via the /oauth/userinfo endpoint. That's it.
See Integration Guide for the full walkthrough.
| Method | Endpoint | Description |
|---|---|---|
GET |
/.well-known/oauth-authorization-server |
Authorization server metadata (RFC 8414) |
POST |
/oauth/register |
Dynamic client registration (RFC 7591) |
GET |
/oauth/authorize |
Authorization + consent UI |
POST |
/oauth/token |
Code -> token exchange with PKCE |
POST |
/oauth/revoke |
Token revocation |
GET |
/oauth/userinfo |
User info from access token |
GET |
/prm/:server_id |
Auto-generated Protected Resource Metadata (RFC 9728) |
POST |
/api/servers |
Register an MCP server (admin) |
GET |
/health |
Health check |
See API Reference for request/response details.
| Type | Prefix | Lifetime | Example |
|---|---|---|---|
| Access token | mat_ |
1 hour | mat_dhcbqsgb... |
| Refresh token | mrt_ |
30 days | mrt_ydqd0ug1... |
| Auth code | code_ |
10 minutes | code_zkm6ukm... |
| Server API key | sak_ |
Permanent | sak_6rvstdl7... |
All tokens are hashed (SHA-256) before storage. The plaintext is only returned once at creation.
mcp-authkit/
src/
worker.js The entire OAuth gateway (~600 lines)
docs/
integration.md How to wire up your MCP server
api.md Full API reference
how-it-works.md Deep dive on the OAuth flow
decisions.md Why we built it this way
war-story.md 10 attempts, every bug, the full timeline
scripts/
test-flow.sh End-to-end OAuth flow test
schema.sql D1 database schema (7 tables)
wrangler.toml Cloudflare Worker config
package.json
This project exists because we spent 10 attempts across 5 days trying to get MCP OAuth working in a Next.js app with Better Auth. Trailing newlines in env vars, boolean-vs-string consent redirects, hashed tokens compared as raw strings, missing OPTIONS handlers, undocumented config flags -- every bug manifested as "nothing happens."
The turning point was realizing OAuth is infrastructure, not product. Rip it out.
- The War Story -- All 10 attempts, every bug, the full timeline
- Architecture Decisions -- Why Cloudflare Workers, why standalone
This is a reference implementation that powers a real product. It is not:
- A maintained library with SLAs
- A drop-in replacement for Auth0/Stytch/Clerk
- Battle-tested at massive scale (it works for our traffic)
Use it to learn from, fork it, steal the patterns. If you need production auth with support, use a dedicated auth provider.
OpZero is an AI-native deployment platform. Ship websites to Cloudflare, Netlify, and Vercel from any MCP client -- Claude, Cursor, or your own agents.
AuthKit is the OAuth layer that powers it. We open-sourced it because every MCP builder shouldn't have to fight the same spec.
- opzero.sh -- Deploy from AI
- UAT Engine -- AI-native testing over MCP (also open source)
- @OpZero-sh -- More from OpZero
Contributions welcome. See CONTRIBUTING.md for guidelines.
Built by @jcameronjeff for OpZero.sh
If this saves you the OAuth headache it saved us, give OpZero a look --
it's the deployment platform we built this for.