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:
-
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
- 64-bit Steam account ID (e.g.,
-
MongoDB ID (
userIdor_id) - Internal database reference- MongoDB ObjectId (e.g.,
507f1f77bcf86cd799439011) - Used internally for database operations
- Not exposed in WebSocket communications
- MongoDB ObjectId (e.g.,
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:
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:
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:
- Extracts token from query parameter or cookie
- Verifies the JWT token
- Extracts
steamIdfrom the token payload - 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
- Navigate to:
http://localhost:3000/auth/steam - Log in with Steam
- Token is automatically stored in cookies
- Connect to WebSocket (token read from cookie)
Option 2: Extract Token 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
-
Open
test-client.htmlin your browser -
For Anonymous Testing:
- Leave "Access Token" field empty
- Click "Connect"
-
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:
- Log in again via
/auth/steam - Get a fresh token
- Reconnect to WebSocket
Connected as Anonymous Instead of Authenticated
Cause: Token not being sent correctly
Solution:
- Verify token is in query parameter:
?token=YOUR_TOKEN - Or verify token is in cookie header
- 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
}
📚 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
// 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).