Files
TurboTrades/WEBSOCKET_AUTH.md
2026-01-10 04:57:43 +00:00

8.3 KiB

🔐 WebSocket Authentication Guide

Overview

The TurboTrades WebSocket system uses Steam ID as the primary user identifier, not MongoDB's internal _id. This guide explains how authentication works and how to connect to the WebSocket server.


🎯 User Identification

Steam ID vs MongoDB ID

The system uses two identifiers for each user:

  1. Steam ID (steamId) - Primary identifier

    • 64-bit Steam account ID (e.g., 76561198012345678)
    • Used for WebSocket connections
    • Used in API responses
    • Canonical user identifier throughout the application
  2. MongoDB ID (userId or _id) - Internal database reference

    • MongoDB ObjectId (e.g., 507f1f77bcf86cd799439011)
    • Used internally for database operations
    • Not exposed in WebSocket communications

Why Steam ID?

  • Consistent: Same ID across all Steam services
  • Public: Can be used to link to Steam profiles
  • Permanent: Never changes, unlike username
  • Standard: Expected identifier in a Steam marketplace

🔌 Connecting to WebSocket

Connection URL

ws://localhost:3000/ws

In production:

wss://your-domain.com/ws

Authentication Methods

Add your JWT access token as a query parameter:

const token = "your-jwt-access-token";
const ws = new WebSocket(`ws://localhost:3000/ws?token=${token}`);

After logging in via Steam (/auth/steam), the access token is stored in a cookie. Simply connect without parameters:

const ws = new WebSocket('ws://localhost:3000/ws');

The server automatically reads the accessToken cookie.

3. Anonymous (Public Access)

Connect without any authentication:

const ws = new WebSocket('ws://localhost:3000/ws');

You'll be connected as a public/anonymous user without access to authenticated features.


🎫 JWT Token Structure

The JWT access token contains:

{
  "userId": "507f1f77bcf86cd799439011",
  "steamId": "76561198012345678",
  "username": "PlayerName",
  "avatar": "https://steamcdn-a.akamaihd.net/...",
  "staffLevel": 0,
  "iat": 1234567890,
  "exp": 1234568790,
  "iss": "turbotrades",
  "aud": "turbotrades-api"
}

Note: The WebSocket system uses steamId from this payload for user identification.


🔄 Connection Flow

1. Client Connects

const ws = new WebSocket('ws://localhost:3000/ws?token=YOUR_TOKEN');

ws.onopen = () => {
  console.log('Connected to WebSocket');
};

2. Server Authenticates

The server:

  1. Extracts token from query parameter or cookie
  2. Verifies the JWT token
  3. Extracts steamId from the token payload
  4. Maps the WebSocket connection to the Steam ID

3. Welcome Message

If authenticated, you receive:

{
  "type": "connected",
  "data": {
    "steamId": "76561198012345678",
    "username": "PlayerName",
    "userId": "507f1f77bcf86cd799439011",
    "timestamp": 1234567890000
  }
}

If anonymous:

⚠️ WebSocket connection without authentication (public)

🔑 Getting an Access Token

Option 1: Steam Login

  1. Navigate to: http://localhost:3000/auth/steam
  2. Log in with Steam
  3. Token is automatically stored in cookies
  4. Connect to WebSocket (token read from cookie)

After logging in, extract the token from your browser:

// In browser console
document.cookie.split('; ')
  .find(row => row.startsWith('accessToken='))
  .split('=')[1];

Option 3: Debug Endpoint

Use the debug endpoint to see your token:

curl http://localhost:3000/auth/decode-token \
  --cookie "accessToken=YOUR_COOKIE_VALUE"

📡 Server-Side API

Sending to Specific User (by Steam ID)

import { wsManager } from './utils/websocket.js';

// Send to user by Steam ID
const steamId = '76561198012345678';
wsManager.sendToUser(steamId, {
  type: 'notification',
  data: { message: 'Your item sold!' }
});

Checking if User is Online

const steamId = '76561198012345678';
const isOnline = wsManager.isUserConnected(steamId);

if (isOnline) {
  wsManager.sendToUser(steamId, {
    type: 'trade_offer',
    data: { offerId: '12345' }
  });
}

Getting User Metadata

const steamId = '76561198012345678';
const metadata = wsManager.getUserMetadata(steamId);

console.log(metadata);
// {
//   steamId: '76561198012345678',
//   connectedAt: 1234567890000,
//   lastActivity: 1234567900000
// }

Broadcasting (Excluding Users)

// Broadcast to all except the user who triggered the action
const excludeSteamIds = ['76561198012345678'];

wsManager.broadcastToAll(
  {
    type: 'listing_update',
    data: { listingId: '123', price: 99.99 }
  },
  excludeSteamIds
);

🧪 Testing with Test Client

Using test-client.html

  1. Open test-client.html in your browser

  2. For Anonymous Testing:

    • Leave "Access Token" field empty
    • Click "Connect"
  3. For Authenticated Testing:

    • Get your access token (see "Getting an Access Token" above)
    • Paste it in the "Access Token" field
    • Click "Connect"
    • You should see your Steam ID in the welcome message

Expected Results

Anonymous Connection:

Server log: ⚠️ WebSocket connection without authentication (public)
Client receives: Connection successful

Authenticated Connection:

Server log: ✅ WebSocket authenticated for user: 76561198012345678 (PlayerName)
Client receives:
{
  "type": "connected",
  "data": {
    "steamId": "76561198012345678",
    "username": "PlayerName",
    "userId": "507f...",
    "timestamp": 1234567890000
  }
}

🔒 Security Considerations

Token Expiry

  • Access tokens expire after 15 minutes
  • Refresh tokens expire after 7 days
  • WebSocket connections persist until disconnected
  • If token expires, reconnect with a fresh token

HTTPS/WSS in Production

Always use secure connections in production:

// Development
ws://localhost:3000/ws

// Production
wss://turbotrades.com/ws

Rate Limiting

Consider implementing rate limiting on WebSocket connections:

  • Max connections per IP
  • Max messages per second
  • Reconnection throttling

🐛 Troubleshooting

"Invalid access token" Error

Cause: Token is expired or malformed

Solution:

  1. Log in again via /auth/steam
  2. Get a fresh token
  3. Reconnect to WebSocket

Connected as Anonymous Instead of Authenticated

Cause: Token not being sent correctly

Solution:

  1. Verify token is in query parameter: ?token=YOUR_TOKEN
  2. Or verify token is in cookie header
  3. Check server logs for authentication errors

Can't Send Message to User

Cause: Using MongoDB ID instead of Steam ID

Solution:

// ❌ Wrong - using MongoDB ID
wsManager.sendToUser('507f1f77bcf86cd799439011', data);

// ✅ Correct - using Steam ID
wsManager.sendToUser('76561198012345678', data);

User Not Receiving Messages

Cause: User is not connected or using wrong Steam ID

Solution:

// Check if user is online first
const steamId = '76561198012345678';
if (wsManager.isUserConnected(steamId)) {
  wsManager.sendToUser(steamId, data);
} else {
  console.log('User is not connected');
  // Store message for later or send via other means
}

  • WEBSOCKET_GUIDE.md - Complete WebSocket feature guide
  • README.md - General project setup
  • QUICK_REFERENCE.md - Quick API reference
  • STEAM_SETUP.md - Steam API key setup

🎯 Quick Reference

Client Connection

// With token
const ws = new WebSocket('ws://localhost:3000/ws?token=YOUR_TOKEN');

// Anonymous
const ws = new WebSocket('ws://localhost:3000/ws');

Server API

// Send to user
wsManager.sendToUser(steamId, messageObject);

// Check if online
wsManager.isUserConnected(steamId);

// Get metadata
wsManager.getUserMetadata(steamId);

// Broadcast
wsManager.broadcastToAll(messageObject, excludeSteamIds);

Token Locations

  • Query parameter: ?token=YOUR_TOKEN
  • Cookie: accessToken=YOUR_TOKEN
  • Header: Authorization: Bearer YOUR_TOKEN (API only)

Key Takeaway: Always use Steam ID (steamId) for WebSocket user identification, not MongoDB's _id (userId).