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

401 lines
8.3 KiB
Markdown

# 🔐 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`).