Skip to main content

WebSocket Authentication Fix

Problem

The WebSocket connection to the pipeline service was failing with immediate 1006 closures (connection rejected by server). The error logs showed:

[Pipeline] ⚠️  EARLY CLOSURE: Connection closed within 111ms - this suggests:
1. Server rejected the connection immediately
2. Network/firewall blocking the connection
3. Invalid WebSocket endpoint or authentication
4. Server overload or rate limiting

Root Cause

The issue was that the browser WebSocket API doesn't support custom headers (like Authorization: Bearer <token>) in the constructor, unlike the Python websockets library which supports additional_headers:

# Python code (works with headers)
headers = {
'Authorization': 'Bearer devtoken',
'X-Auth-Token': 'devtoken',
}

async with websockets.connect(uri, ssl=ssl_context, additional_headers=headers) as websocket:
// Browser WebSocket API (doesn't support headers)
const ws = new WebSocket(url); // No way to pass Authorization header

Solution

Modified the usePipeline hook to include the authentication token as a URL query parameter instead of attempting to send it via headers:

Changes Made

  1. Authentication Token Retrieval: Extract the token from localStorage (same as used by Apollo GraphQL client)
const token = typeof window !== 'undefined' ? localStorage.getItem('token') : 'devtoken';
const authToken = token || 'devtoken'; // Fallback to devtoken for development
  1. URL Parameter Authentication: Include the token in the WebSocket URL
const url = `${wsUrl}/ws/pipeline/${clientId}?token=${encodeURIComponent(authToken)}`;
  1. Enhanced Logging: Log authentication status for debugging
authToken: authToken ? `${authToken.substring(0, 10)}...` : 'none',
hasToken: !!authToken,
  1. Consistent Auth Message: Use the same token in the post-connection auth message
const authMessage = {
type: 'auth',
client_id: clientId,
token: authToken, // Use the same token that was passed in the URL
timestamp: Date.now()
};

How This Matches Your Python Example

Your Python code structure:

# URL with session ID
uri = f'wss://pipeline.aiqlick.com/ws/pipeline/{session_id}'

# Headers with authentication
headers = {
'Authorization': 'Bearer devtoken',
'X-Auth-Token': 'devtoken',
}

# Connection with both
async with websockets.connect(uri, ssl=ssl_context, additional_headers=headers) as websocket:
# Send message after connection
message = {
'pipeline_type': 'test',
'payload': { /* ... */ }
}

Updated TypeScript equivalent:

// URL with session ID AND authentication token
const url = `wss://pipeline.aiqlick.com/ws/pipeline/${clientId}?token=${encodeURIComponent(authToken)}`;

// Connection (authentication in URL since headers not supported)
const ws = new WebSocket(url);

ws.onopen = () => {
// Send auth message after connection (similar to your message sending)
const authMessage = {
type: 'auth',
client_id: clientId,
token: authToken,
timestamp: Date.now()
};
ws.send(JSON.stringify(authMessage));
};

Expected Results

With these changes, the WebSocket connection should:

  1. Pass Authentication: The server can now validate the token from the URL parameter
  2. Reduce 1006 Errors: Server should accept the connection instead of immediately rejecting it
  3. Match Python Behavior: Provides equivalent authentication to your working Python example
  4. Maintain Fallback: Uses 'devtoken' for development environments

Server-Side Requirements

The pipeline server needs to:

  1. Accept the token query parameter in the WebSocket endpoint
  2. Validate the token before accepting the connection
  3. Handle both URL parameter auth and the subsequent auth message

Testing

To verify the fix is working:

  1. Check browser dev tools Network tab for the WebSocket connection
  2. Verify the URL includes ?token=...
  3. Connection should stay open longer than 111ms
  4. Look for successful authentication messages in the console

Migration Notes

This change is backward compatible:

  • Existing connections without tokens will still get 'devtoken' as fallback
  • The auth message is still sent after connection for additional validation
  • No changes needed to how the hook is used in components