Skip to main content

Choosing an Authentication Method

Poke supports four authentication patterns based on the MCP Authorization specification. The right choice depends on what you’re building:
MethodWhat You’re BuildingUser ExperienceImplementationExample
Remote OAuth with DCRA brand new service (your own data, your own users)One-time auth, automatic refreshUse an identity provider (WorkOS, Auth0)Bookmark Manager
OAuth ProxyConnecting an existing API (WHOOP, GitHub, Strava, etc.)One-time auth, automatic refreshProxy to the existing OAuth providerWHOOP Integration
API KeySimple auth for testing or developer toolsPaste key onceValidate bearer tokensWeather API
No AuthPublic data with no user identityZero setupNo authentication neededISS Tracker

Understanding the Two OAuth Patterns

Remote OAuth with DCR: Building Your Own Service Use this when you’re creating something new and you need user management. You control the data, and users are authenticating with your service.
  • What you’re building: A new integration where you store and manage user data
  • How it works: Use an identity provider like WorkOS AuthKit to handle the OAuth server (they manage user accounts, tokens, sessions)
  • Your responsibility: Build your tools and validate tokens from the IdP
  • MCP clients: Can register automatically (Dynamic Client Registration)
  • Example: See the Bookmark Manager for a complete implementation where users save personal bookmarks to your database
OAuth Proxy: Wrapping an Existing Service Use this when you’re making an existing third-party API available through Poke - WHOOP health data, GitHub repos, Strava workouts. The service already has OAuth; you’re just connecting to it.
  • What you’re building: An integration that wraps an existing API with existing OAuth
  • How it works: Your MCP server acts as a bridge between Poke and the upstream OAuth provider
  • Your responsibility: Create an OAuth app in the provider’s dashboard, configure FastMCP’s proxy, validate upstream tokens
  • MCP clients: Connect through your server’s proxy (your server handles the DCR part)
  • Example: See the WHOOP Integration where users authenticate with WHOOP and you fetch their health data
Simple rule: If you’re building something new from scratch, use Remote OAuth with DCR. If you’re connecting an existing service that already has OAuth, use OAuth Proxy.

OAuth 2.1 with PKCE

Both OAuth patterns use the same OAuth 2.1 + PKCE flow. The difference is in who manages authentication:
  • Remote OAuth with DCR: An identity provider (WorkOS, Auth0) handles authentication for your new service
  • OAuth Proxy: You proxy through to an existing service’s OAuth provider (WHOOP, GitHub, etc.)

What Poke Does as a Client (Same for Both Patterns)

When a user connects your integration, Poke:
  1. Discovers your OAuth config via the .well-known endpoint
  2. Handles PKCE flow (generates code_verifier and code_challenge)
  3. Redirects users to your authorization URL
  4. Exchanges authorization codes for tokens
  5. Stores tokens securely (encrypted at rest)
  6. Refreshes tokens automatically before expiration
  7. Includes tokens in every request via Authorization: Bearer header
You focus on building your authorization logic. Poke handles the client-side OAuth mechanics.

Complete OAuth Flow

Key MCP requirements Poke implements:
  • RFC 9728 (Protected Resource Metadata) for authorization server discovery
  • RFC 8414 (Authorization Server Metadata) for endpoint discovery
  • RFC 7591 (Dynamic Client Registration) when your server supports it
  • PKCE with S256 (mandatory per MCP spec) for authorization code protection
  • Resource parameter (RFC 8707) to bind tokens to your specific server

FastMCP OAuth Implementations

FastMCP provides different OAuth implementations depending on what you’re building:

For New Services: AuthKitProvider (Remote OAuth with DCR)

When building a new service from scratch, use AuthKitProvider to integrate with WorkOS AuthKit:
from fastmcp import FastMCP
from fastmcp.server.auth.providers.workos import AuthKitProvider

auth = AuthKitProvider(
    authkit_domain="https://your-project.authkit.app",
    base_url="https://your-server.com"
)

mcp = FastMCP("Bookmark Manager", auth=auth)

@mcp.tool(description="Add bookmark for authenticated user")
async def add_bookmark(url: str, title: str) -> dict:
    # Get authenticated user's token claims
    token = get_access_token()
    user_id = token.claims["sub"]

    # Store bookmark scoped to this user
    return save_bookmark(user_id, url, title)
What AuthKitProvider handles:
  • JWT token validation via JWKS
  • Protected resource metadata (/.well-known/oauth-protected-resource)
  • Authorization server discovery
  • User identity via token claims
Full example: See Bookmark Manager for a complete AuthKitProvider implementation with per-user data storage.

For Existing Services: OAuthProxy

When wrapping an existing OAuth provider (WHOOP, GitHub, etc.), use OAuthProxy:
from fastmcp import FastMCP
from fastmcp.server.auth import OAuthProxy

auth = OAuthProxy(
    upstream_authorization_endpoint="https://api.whoop.com/oauth/oauth2/auth",
    upstream_token_endpoint="https://api.whoop.com/oauth/oauth2/token",
    upstream_client_id=os.environ["WHOOP_CLIENT_ID"],
    upstream_client_secret=os.environ["WHOOP_CLIENT_SECRET"],
    forward_pkce=True,
    base_url=os.environ["PUBLIC_BASE_URL"]
)

mcp = FastMCP("WHOOP Integration", auth=auth)

@mcp.tool(description="Get user's WHOOP recovery data")
async def get_recovery() -> dict:
    # Get token to call upstream WHOOP API
    token = get_access_token()

    # Call WHOOP API with user's token
    async with httpx.AsyncClient() as client:
        response = await client.get(
            "https://api.whoop.com/developer/v2/recovery",
            headers={"Authorization": f"Bearer {token.token}"}
        )
        return response.json()
What OAuthProxy handles:
  • Proxying OAuth flow to upstream provider
  • PKCE between Poke and your server, and your server and upstream
  • Token exchange with upstream provider
  • Exposing MCP-compatible discovery endpoints
Full example: See WHOOP Integration for a complete OAuthProxy implementation that fetches user health data from WHOOP’s API. For an overview of all four authentication patterns with deployment instructions, see Authentication Examples.

OAuth Discovery: What Poke Expects

When Poke connects to your server and receives a 401 response, it expects specific metadata endpoints:

1. Protected Resource Metadata

Endpoint: GET /.well-known/oauth-protected-resource Response:
{
  "resource": "https://your-server.com",
  "authorization_servers": ["https://auth.your-server.com"]
}

2. Authorization Server Metadata

Endpoint: GET /.well-known/oauth-authorization-server Response:
{
  "issuer": "https://auth.your-server.com",
  "authorization_endpoint": "https://auth.your-server.com/authorize",
  "token_endpoint": "https://auth.your-server.com/token",
  "code_challenge_methods_supported": ["S256"],
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "response_types_supported": ["code"],
  "token_endpoint_auth_methods_supported": ["none", "client_secret_post"]
}
FastMCP generates these endpoints automatically when you configure OAuth.

Token Validation

Your MCP server must validate access tokens on every request:
from fastmcp import FastMCP

mcp = FastMCP("My Service", auth=oauth_config)

# FastMCP automatically validates tokens
# Rejects requests with invalid/expired tokens with 401
# Provides authenticated context to your tools
If implementing token validation manually:
  1. Verify token signature (if using JWT)
  2. Check expiration (exp claim)
  3. Validate audience (token issued for your server)
  4. Verify scopes (user authorized required permissions)
  5. Return 401 if any check fails

Token Refresh

Poke automatically refreshes tokens before expiration. Your authorization server must support refresh tokens: Request Poke makes:
POST /token HTTP/1.1
Host: auth.your-server.com
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&
refresh_token=<refresh_token>&
client_id=<client_id>&
resource=https://your-server.com
Expected response:
{
  "access_token": "new_token",
  "refresh_token": "new_refresh_token",
  "expires_in": 3600,
  "token_type": "Bearer"
}
FastMCP handles refresh token logic automatically.

When Users Should See OAuth

OAuth prompts should appear:
  • First connection: User hasn’t authorized your integration yet
  • Token expired: Refresh token is invalid/expired, requires reauthorization
  • Scope changes: You request additional permissions
Poke stores the OAuth state and will automatically redirect users through the flow when needed.

API Key Authentication

What You Build

An MCP server that accepts bearer tokens in the Authorization header and validates them against your backend.
from fastmcp import FastMCP
import os

mcp = FastMCP("My Service")

@mcp.tool(description="Get user data")
async def get_data() -> dict:
    # Access API key from request context
    api_key = mcp.get_auth_header()

    # Validate against your backend
    user = await validate_api_key(api_key)
    if not user:
        raise ValueError("Invalid API key")

    return fetch_user_data(user.id)
See it in action: The Weather API example shows how to validate user-provided API keys and proxy requests to an upstream weather service.

What Poke Does

When a user adds your integration with an API key:
  1. User pastes API key in Poke’s connection form
  2. Poke tests connection by calling your server with Authorization: Bearer <key>
  3. If validation succeeds, integration is connected
  4. Every subsequent request includes the same header

Request Format

POST /mcp HTTP/1.1
Host: your-server.com
Authorization: Bearer sk_your_api_key_here
Content-Type: application/json

{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": { ... }
}

Validation Best Practices

Do:
  • Validate keys on every request
  • Return 401 for invalid/expired keys
  • Rate limit by API key
  • Support key rotation (users can update keys in Poke)
Don’t:
  • Accept keys in URL query parameters (security risk)
  • Log API keys (store hashed versions)
  • Return detailed error messages (prevents enumeration attacks)

When to Use API Keys

Good fit:
  • Developer/power user integrations
  • Services with existing API key infrastructure
  • Testing and development environments
  • Read-only or low-risk operations
Not recommended:
  • Consumer-facing apps (OAuth is better UX)
  • High-security requirements (keys don’t expire)
  • Per-user data access (hard to manage at scale)

No Authentication

What You Build

An MCP server that returns data without checking authentication.
from fastmcp import FastMCP
import httpx

mcp = FastMCP("Public Data Service")

@mcp.tool(description="Get current weather")
async def get_weather(city: str) -> dict:
    async with httpx.AsyncClient() as client:
        response = await client.get(
            f"https://api.weather.com/v1/current?city={city}"
        )
        return response.json()
See it in action: The ISS Tracker example demonstrates a complete no-auth integration that provides real-time space station location data.

What Poke Does

  1. User enters server URL
  2. Poke connects immediately (no auth prompt)
  3. Tools are available for all users

When to Use No Auth

Good fit:
  • Public APIs (weather, astronomy, public datasets)
  • Read-only data that’s not sensitive
  • Internal tools within trusted networks
  • Testing and demos
Security considerations:
  • Rate limit by IP to prevent abuse
  • Monitor usage patterns for anomalies
  • Consider API keys if abuse occurs
  • Document public endpoints clearly

Security Best Practices

For All Authentication Methods

  1. Use HTTPS everywhere (required by MCP spec for production)
  2. Validate all inputs before processing requests
  3. Rate limit requests to prevent abuse
  4. Log authentication failures for monitoring
  5. Rotate secrets regularly (OAuth secrets, API keys, encryption keys)

OAuth-Specific

  1. Implement PKCE (mandatory for public clients)
  2. Validate state parameter to prevent CSRF attacks
  3. Bind tokens to audience (verify tokens are for your server)
  4. Issue short-lived access tokens (15-60 minutes recommended)
  5. Rotate refresh tokens on each use
  6. Never pass through tokens to third-party APIs (see MCP Security Best Practices)

API Key-Specific

  1. Hash keys in database (don’t store plaintext)
  2. Use cryptographically secure random generation (e.g., secrets.token_urlsafe())
  3. Prefix keys with identifiable strings (e.g., sk_live_, sk_test_)
  4. Implement key expiration if possible
  5. Allow users to revoke keys easily

No Auth-Specific

  1. Rate limit aggressively (per IP, per user agent)
  2. Cache responses to reduce load
  3. Monitor for scraping (unusual access patterns)
  4. Implement CORS properly if browser-based
  5. Document acceptable use clearly

Testing Authentication

OAuth Testing

Local development:
# Use MCP Inspector for local OAuth testing
npm install -g @modelcontextprotocol/inspector
mcp-inspector --config your-server-config.json
Integration testing:
  1. Deploy to HTTPS endpoint (OAuth requires secure redirects)
  2. Test full flow in Poke’s integration tester
  3. Verify token refresh works correctly
  4. Test error cases (denied access, expired tokens)
Try the examples: The WHOOP Integration and Bookmark Manager examples include complete OAuth implementations you can deploy and test immediately.

API Key Testing

# Test valid key
curl -H "Authorization: Bearer sk_test_valid_key" \
     https://your-server.com/mcp

# Test invalid key (should return 401)
curl -H "Authorization: Bearer sk_test_invalid_key" \
     https://your-server.com/mcp

No Auth Testing

# Should work without Authorization header
curl https://your-server.com/mcp

How Authentication Interacts with Tool Lifecycle

Authentication happens before tool discovery: Key points:
  • Tools discovered once after successful authentication
  • Auth credentials used for all subsequent tool calls
  • Reconnection required if auth changes (new scopes, new key)
  • Token refresh transparent to tool execution
See Tool Lifecycle for details on how tools are cached and refreshed.

Next Steps