401 lines
8.3 KiB
Markdown
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`). |