OAuth authentication
OAuth 2.0 with PKCE for custom MCP integrations.
The Doow MCP server uses OAuth 2.0 with PKCE for authentication. Most MCP clients handle this automatically. This page documents the flow for custom integrations.
Discovery endpoints
GET https://mcp.doow.co/.well-known/oauth-authorization-serverGET https://mcp.doow.co/.well-known/oauth-protected-resourceThese return the OAuth metadata including endpoints, supported grants, and PKCE requirements.
Flow overview
1. Register client POST /oauth/register2. Build authorize URL GET /oauth/authorize3. User signs in (browser)4. Handle callback GET your-redirect-uri?code=...5. Exchange code POST /oauth/token6. Call MCP endpoint POST /mcp (with Bearer token)7. Refresh token POST /oauth/token (grant_type=refresh_token)1. Client registration
Register your client with Dynamic Client Registration (RFC 7591):
curl -X POST https://mcp.doow.co/oauth/register \-H "Content-Type: application/json" \-d '{ "client_name": "My MCP Client", "redirect_uris": ["http://127.0.0.1:8080/callback"], "grant_types": ["authorization_code", "refresh_token"], "response_types": ["code"], "token_endpoint_auth_method": "none", "scope": "mcp:read mcp:write"}'Response:
{"client_id": "mcp_abc123...","client_id_issued_at": 1725638400,"redirect_uris": ["http://127.0.0.1:8080/callback"],"grant_types": ["authorization_code", "refresh_token"],"response_types": ["code"],"scope": "mcp:read mcp:write","token_endpoint_auth_method": "none","pkce_required": true}2. Build authorize URL
Generate a PKCE code verifier and challenge:
// Generate 43-128 character verifierconst verifier = crypto.randomBytes(32).toString('base64url');// Create S256 challengeconst challenge = crypto.createHash('sha256').update(verifier).digest('base64url');Build the authorization URL with all required parameters:
https://mcp.doow.co/oauth/authorize?response_type=code&client_id=YOUR_CLIENT_ID&redirect_uri=http://127.0.0.1:8080/callback&scope=mcp:read%20mcp:write&state=RANDOM_STATE&code_challenge=YOUR_CHALLENGE&code_challenge_method=S256&resource=https://mcp.doow.co3. Handle callback
After the user authorizes, they are redirected to your registered callback URL:
http://127.0.0.1:8080/callback?code=AUTH_CODE&state=YOUR_STATEVerify the state matches what you sent, then extract the code.
4. Exchange code for token
curl -X POST https://mcp.doow.co/oauth/token \-H "Content-Type: application/x-www-form-urlencoded" \-d "grant_type=authorization_code" \-d "code=AUTH_CODE" \-d "redirect_uri=http://127.0.0.1:8080/callback" \-d "client_id=YOUR_CLIENT_ID" \-d "code_verifier=YOUR_VERIFIER"Response:
{"access_token": "eyJ...","token_type": "Bearer","expires_in": 3600,"refresh_token": "mcp_rt_...","scope": "mcp:read mcp:write"}5. Call MCP endpoint
Initialize an MCP session:
curl -X POST https://mcp.doow.co/mcp \-H "Authorization: Bearer ACCESS_TOKEN_JWT" \-H "Content-Type: application/json" \-H "Accept: application/json, text/event-stream" \-d '{ "jsonrpc": "2.0", "method": "initialize", "params": { "protocolVersion": "2025-03-26", "capabilities": {}, "clientInfo": { "name": "my-client", "version": "1.0.0" } }, "id": 1}'The response includes an Mcp-Session-Id header. Include this header on all subsequent requests.
6. Refresh token
Access tokens expire after 1 hour, so refresh them before they expire:
curl -X POST https://mcp.doow.co/oauth/token \-H "Content-Type: application/x-www-form-urlencoded" \-d "grant_type=refresh_token" \-d "refresh_token=mcp_rt_..." \-d "client_id=YOUR_CLIENT_ID"Refresh tokens last 30 days of non-use, and using one extends its lifetime.
After refreshing an access token, initialize a new MCP session. The current server does not rebind an existing session to the refreshed token.
Token revocation
Revoke tokens when you are done:
curl -X POST https://mcp.doow.co/oauth/revoke \-H "Content-Type: application/x-www-form-urlencoded" \-d "token=ACCESS_TOKEN_JWT" \-d "client_id=YOUR_CLIENT_ID"Common mistakes
| Mistake | Fix |
|---|---|
Missing resource parameter | Add resource=https://mcp.doow.co to authorize URL |
| Short code verifier | Use 43-128 characters, not the common default of 32 |
| Base64 padding in challenge | Use base64url encoding without trailing = |
| Mixing localhost and 127.0.0.1 | Use the same form in registration and authorize URL |
| Regenerating verifier | Use the same verifier for challenge and token exchange |
Rate limits
| Endpoint | Limit |
|---|---|
/oauth/register | 10 per hour per IP |
/oauth/token | No MCP-specific limiter currently wired |
/oauth/token (refresh) | No MCP-specific limiter currently wired |
/mcp (OAuth member) | 60 per minute per member |
/mcp (MCP API key) | 30 per minute per member |
/mcp (organization) | 500 per minute per organization |
| Tool calls | Tool-specific limits apply where declared; many are 10 per minute |
Rate limit responses include a Retry-After header, while tokens use these formats:
- OAuth access tokens are raw JWTs.
- Refresh tokens start with
mcp_rt_. - MCP API keys start with
doow_mcp_. See the MCP API key section for details.