# ๐Ÿ” 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 #### 1. **Query Parameter** (Recommended for Testing) Add your JWT access token as a query parameter: ```javascript const token = "your-jwt-access-token"; const ws = new WebSocket(`ws://localhost:3000/ws?token=${token}`); ``` #### 2. **Cookie** (Automatic after Login) After logging in via Steam (`/auth/steam`), the access token is stored in a cookie. Simply connect without parameters: ```javascript const ws = new WebSocket('ws://localhost:3000/ws'); ``` The server automatically reads the `accessToken` cookie. #### 3. **Anonymous** (Public Access) Connect without any authentication: ```javascript 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: ```json { "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 ```javascript 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: ```json { "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) ### Option 2: Extract Token from Cookie After logging in, extract the token from your browser: ```javascript // 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: ```bash curl http://localhost:3000/auth/decode-token \ --cookie "accessToken=YOUR_COOKIE_VALUE" ``` --- ## ๐Ÿ“ก Server-Side API ### Sending to Specific User (by Steam ID) ```javascript 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 ```javascript const steamId = '76561198012345678'; const isOnline = wsManager.isUserConnected(steamId); if (isOnline) { wsManager.sendToUser(steamId, { type: 'trade_offer', data: { offerId: '12345' } }); } ``` ### Getting User Metadata ```javascript const steamId = '76561198012345678'; const metadata = wsManager.getUserMetadata(steamId); console.log(metadata); // { // steamId: '76561198012345678', // connectedAt: 1234567890000, // lastActivity: 1234567900000 // } ``` ### Broadcasting (Excluding Users) ```javascript // 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: ```javascript // 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:** ```javascript // โŒ 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:** ```javascript // 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 } ``` --- ## ๐Ÿ“š Related Documentation - **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 ```javascript // With token const ws = new WebSocket('ws://localhost:3000/ws?token=YOUR_TOKEN'); // Anonymous const ws = new WebSocket('ws://localhost:3000/ws'); ``` ### Server API ```javascript // 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`).