Connect Tokens
HMAC-signed tokens that prevent unauthenticated credential injection and OAuth flow abuse.
Overview
The connect flow lets end-users submit credentials (API keys, tokens) or start OAuth authorization for a plugin. These endpoints need to be publicly accessible because the user's browser hits them directly, but they also need to be tied back to an authenticated session.
Connect tokens solve this by binding every connect flow to a specific developer, user, and plugin with a time-limited HMAC signature.
The Problem
Without connect tokens, the credential submission endpoints accept a developerId, userId, and pluginSlug in the request body. An attacker who knows these values could:
- Submit fake credentials for another user
- Overwrite legitimate credentials with their own
- Initiate OAuth flows on behalf of arbitrary users
How It Works
Token Generation
When the SDK or dashboard calls the connection initiation endpoint, the server generates a connect token:
const token = generateConnectToken(developerId, userId, pluginSlug);
// returns: base64url(payload) + "." + hmac_signatureThe payload contains:
| Field | Description |
|---|---|
developerId | The developer who owns the plugin |
userId | The end-user being connected |
pluginSlug | The target plugin |
exp | Expiration timestamp (30 minutes from creation) |
nonce | Random value to prevent replay |
The payload is signed with HMAC-SHA256 using ENCRYPTION_KEY.
Token Verification
When the user submits credentials or starts an OAuth flow, the server verifies:
- The signature is valid (not tampered with)
- The token has not expired
- The
developerId,userId, andpluginSlugmatch the request
If any check fails, the request is rejected with a 403.
Protected Endpoints
POST /api/connect/submit
Accepts direct credential submissions (API keys, bearer tokens). The request body must include a connectToken field:
{
"developerId": "uuid",
"userId": "user_123",
"pluginSlug": "slack",
"authMethodId": "uuid",
"credentials": { "api_key": "xoxb-..." },
"connectToken": "eyJ...signature"
}POST /api/connect/oauth-start
Initiates OAuth authorization. The connect token ensures that only legitimately initiated flows can start an OAuth handshake. The mcpClientSecret is sent in the POST body instead of as a query parameter, preventing accidental logging of secrets in server access logs.
Token Flow
1. SDK calls POST /api/v1/connections/initiate (authenticated via API key)
→ Server generates connectToken
→ Returns connect page URL with token embedded
2. User lands on /connect/[plugin] page
→ Page receives connectToken from URL
3. User submits credentials → POST /api/connect/submit { connectToken, ... }
→ Server verifies token → accepts or rejects
OR
User starts OAuth → POST /api/connect/oauth-start { connectToken, ... }
→ Server verifies token → redirects to OAuth providerExpiration
Tokens expire after 30 minutes by default. This window is long enough for a user to complete the connect flow but short enough to limit the attack surface if a token leaks.
Relationship to OAuth State Tokens
Connect tokens and OAuth state tokens serve different purposes:
| Connect Token | OAuth State Token | |
|---|---|---|
| When | Before OAuth or direct submission | During OAuth redirect flow |
| Purpose | Binds the connect flow to an authenticated session | Prevents CSRF during OAuth callback |
| Signed with | HMAC-SHA256 (ENCRYPTION_KEY) | HMAC-SHA256 (ENCRYPTION_KEY) |
| Lifetime | 30 minutes | Duration of the OAuth redirect |
| Verified at | /api/connect/submit, /api/connect/oauth-start | /api/v1/oauth/callback |