first commit
This commit is contained in:
401
WEBSOCKET_AUTH.md
Normal file
401
WEBSOCKET_AUTH.md
Normal file
@@ -0,0 +1,401 @@
|
||||
# 🔐 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`).
|
||||
Reference in New Issue
Block a user