Skip to content

Latest commit

 

History

History
496 lines (367 loc) · 10.5 KB

File metadata and controls

496 lines (367 loc) · 10.5 KB

OAuth 2.1 Authentication Guide

This guide covers the OAuth 2.1 authentication implementation for the Twenty MCP Server, following the MCP Authentication Specification.

Table of Contents

Quick Start

1. Run OAuth Setup CLI

npm run setup:oauth

This interactive CLI will guide you through:

  • Enabling OAuth authentication
  • Configuring Clerk credentials
  • Setting up encryption
  • Configuring default API keys

2. Start the Server

npm start

3. Test OAuth Discovery

# Check OAuth protected resource metadata
curl http://localhost:3000/.well-known/oauth-protected-resource

# Check authorization server metadata
curl http://localhost:3000/.well-known/oauth-authorization-server

Architecture Overview

sequenceDiagram
    participant Client as MCP Client
    participant Server as Twenty MCP Server
    participant Clerk as Clerk Auth
    participant Twenty as Twenty CRM

    Client->>Server: 1. Discover OAuth endpoints
    Server->>Client: 2. Return discovery metadata
    
    Client->>Clerk: 3. OAuth authorization flow
    Clerk->>Client: 4. Return access token
    
    Client->>Server: 5. Store API key (with Bearer token)
    Server->>Clerk: 6. Validate token & get user ID
    Server->>Server: 7. Encrypt & store user's Twenty API key
    
    Client->>Server: 8. MCP request (with Bearer token)
    Server->>Clerk: 9. Validate token
    Server->>Server: 10. Decrypt user's API key
    Server->>Twenty: 11. Make Twenty API call
    Twenty->>Server: 12. Return data
    Server->>Client: 13. Return MCP response
Loading

Setup and Configuration

Environment Variables

Variable Required Description
AUTH_ENABLED No Enable OAuth authentication (default: false)
REQUIRE_AUTH No Require auth for all requests (default: false)
AUTH_PROVIDER No Auth provider (currently only 'clerk')
CLERK_PUBLISHABLE_KEY If auth enabled Clerk publishable key
CLERK_SECRET_KEY If auth enabled Clerk secret key
CLERK_DOMAIN If auth enabled Clerk domain (auto-detected if not set)
API_KEY_ENCRYPTION_SECRET If auth enabled 32+ character secret for encrypting API keys
TWENTY_API_KEY No Global Twenty API key (fallback)
TWENTY_BASE_URL No Twenty CRM base URL (default: https://api.twenty.com)

Manual Configuration

Create a .env file:

# OAuth Configuration
AUTH_ENABLED=true
REQUIRE_AUTH=false
AUTH_PROVIDER=clerk

# Clerk Configuration
CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...
CLERK_DOMAIN=your-app.clerk.accounts.dev

# Security
API_KEY_ENCRYPTION_SECRET=your-32-byte-hex-string-here

# Optional: Global Twenty API Key
TWENTY_API_KEY=your-twenty-api-key
TWENTY_BASE_URL=https://api.twenty.com

OAuth Flow

1. Discovery Phase

MCP clients discover OAuth capabilities by calling:

GET /.well-known/oauth-protected-resource

Response:

{
  "resource": "http://localhost:3000",
  "authorization_servers": ["https://your-app.clerk.accounts.dev"],
  "bearer_methods_supported": ["header"],
  "scopes_supported": ["twenty:read", "twenty:write"]
}

2. Authorization Server Discovery

GET /.well-known/oauth-authorization-server

Response:

{
  "issuer": "https://your-app.clerk.accounts.dev",
  "authorization_endpoint": "https://your-app.clerk.accounts.dev/oauth/authorize",
  "token_endpoint": "https://your-app.clerk.accounts.dev/oauth/token",
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "code_challenge_methods_supported": ["S256"]
}

3. User Authorization

Client redirects user to Clerk's authorization endpoint:

https://your-app.clerk.accounts.dev/oauth/authorize
  ?response_type=code
  &client_id=YOUR_CLIENT_ID
  &redirect_uri=YOUR_REDIRECT_URI
  &scope=twenty:read+twenty:write
  &code_challenge=CHALLENGE
  &code_challenge_method=S256

4. Token Exchange

Client exchanges authorization code for access token:

POST https://your-app.clerk.accounts.dev/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=AUTHORIZATION_CODE
&client_id=YOUR_CLIENT_ID
&code_verifier=VERIFIER
&redirect_uri=YOUR_REDIRECT_URI

5. API Key Configuration

Store user's Twenty API key (one-time setup):

POST /api/keys
Authorization: Bearer ACCESS_TOKEN
Content-Type: application/json

{
  "apiKey": "user-twenty-api-key",
  "baseUrl": "https://api.twenty.com"
}

6. Authenticated MCP Requests

Make MCP requests with Bearer token:

POST /mcp
Authorization: Bearer ACCESS_TOKEN
Content-Type: application/json

{
  "jsonrpc": "2.0",
  "method": "initialize",
  "params": {},
  "id": 1
}

API Endpoints

Health Check

GET /health

Returns server status and auth configuration.

OAuth Discovery

  • GET /.well-known/oauth-protected-resource - Protected resource metadata
  • GET /.well-known/oauth-authorization-server - Authorization server metadata

API Key Management

All endpoints require valid Bearer token.

Get API Key Metadata

GET /api/keys
Authorization: Bearer TOKEN

Response:

{
  "hasKey": true,
  "updatedAt": "2024-01-01T00:00:00Z",
  "baseUrl": "https://api.twenty.com"
}

Store API Key

POST /api/keys
Authorization: Bearer TOKEN
Content-Type: application/json

{
  "apiKey": "your-twenty-api-key",
  "baseUrl": "https://api.twenty.com"
}

Delete API Key

DELETE /api/keys
Authorization: Bearer TOKEN

MCP Endpoint

POST /mcp[?apiKey=key]
Authorization: Bearer TOKEN (if auth enabled)
Content-Type: application/json

{
  "jsonrpc": "2.0",
  "method": "method_name",
  "params": {},
  "id": 1
}

Client Integration

MCP Client Example

import { Client } from '@modelcontextprotocol/sdk/client/index.js';

// 1. Discover OAuth endpoints
const discovery = await fetch('http://localhost:3000/.well-known/oauth-protected-resource');
const metadata = await discovery.json();

// 2. Perform OAuth flow (implementation depends on client)
const accessToken = await performOAuthFlow(metadata.authorization_servers[0]);

// 3. Configure API key (one-time)
await fetch('http://localhost:3000/api/keys', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${accessToken}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    apiKey: 'user-twenty-api-key',
    baseUrl: 'https://api.twenty.com'
  })
});

// 4. Create authenticated MCP client
const client = new Client({
  name: 'my-client',
  version: '1.0.0'
}, {
  capabilities: {}
});

// 5. Connect with Bearer token
await client.connect(transport, {
  headers: {
    'Authorization': `Bearer ${accessToken}`
  }
});

Backward Compatibility

The server maintains backward compatibility with non-authenticated requests:

// Works when AUTH_ENABLED=false or REQUIRE_AUTH=false
const url = 'http://localhost:3000/mcp?apiKey=your-api-key';
await client.connect(httpTransport(url));

Security Considerations

API Key Encryption

  • User API keys are encrypted using AES-256-GCM
  • Unique initialization vector (IV) for each encryption
  • Authentication tags prevent tampering
  • Keys are stored in Clerk's secure privateMetadata

Token Validation

  • Bearer tokens are validated with Clerk on every request
  • Session status checked for active sessions
  • JWT signatures verified
  • Tokens cached briefly to reduce Clerk API calls

Transport Security

  • HTTPS required in production
  • CORS headers configured for cross-origin requests
  • Secure HTTP headers recommended

Environment Security

# Secure environment variables
chmod 600 .env

# Use strong encryption secret
openssl rand -hex 32

Troubleshooting

Common Issues

Authentication Failed

Error: Authentication failed - mock token not accepted

Solution: Use real Clerk tokens, not mock tokens.

Missing API Key

Error: No API key configured

Solution: Configure API key via /api/keys endpoint after authentication.

Encryption Secret Missing

Error: API_KEY_ENCRYPTION_SECRET environment variable is required

Solution: Set a 32+ character encryption secret in environment variables.

CORS Issues

Error: CORS policy blocked request

Solution: Server includes CORS headers, but check client configuration.

Debug Mode

Enable debug logging:

DEBUG=clerk:*
NODE_ENV=development

Health Check

curl http://localhost:3000/health

Should return:

{
  "status": "healthy",
  "service": "twenty-mcp-server",
  "authEnabled": true
}

Test OAuth Discovery

# Test protected resource metadata
curl -v http://localhost:3000/.well-known/oauth-protected-resource

# Should return 200 OK when auth is enabled
# Should return 404 when auth is disabled

Validate Clerk Configuration

# Test with real Clerk token
export ACCESS_TOKEN="your-clerk-token"

curl -H "Authorization: Bearer $ACCESS_TOKEN" \
     http://localhost:3000/api/keys

Migration Guide

From Non-Authenticated Setup

  1. Backup existing configuration:

    cp .env .env.backup
  2. Run OAuth setup:

    npm run setup:oauth
  3. Test backward compatibility:

    # Should still work
    curl "http://localhost:3000/mcp?apiKey=your-key"
  4. Enable required auth (optional):

    REQUIRE_AUTH=true

From Development to Production

  1. Update Clerk keys from test to live:

    CLERK_PUBLISHABLE_KEY=pk_live_...
    CLERK_SECRET_KEY=sk_live_...
  2. Use strong encryption secret:

    openssl rand -hex 32
  3. Enable HTTPS and update server URL:

    MCP_SERVER_URL=https://your-domain.com
  4. Review security headers in production deployment.

Support