first commit

This commit is contained in:
2026-01-10 04:57:43 +00:00
parent 16a76a2cd6
commit 232968de1e
131 changed files with 43262 additions and 0 deletions

370
JWT_REFERENCE.md Normal file
View File

@@ -0,0 +1,370 @@
# JWT Token Reference Guide
## 🎯 What's in Your JWT Token
Your JWT tokens now contain all essential user information, so you don't need to make database calls for basic user data.
---
## 📦 Token Payload Contents
### Access Token & Refresh Token Include:
```javascript
{
// User Identification
userId: "507f1f77bcf86cd799439011", // MongoDB _id
steamId: "76561198012345678", // Steam ID64
// Profile Information (NEW!)
username: "YourSteamName", // Display name
avatar: "https://avatars.cloudflare.steamstatic.com/...", // Profile picture URL
// Permissions
staffLevel: 0, // 0=User, 1=Support, 2=Mod, 3=Admin
// JWT Standard Claims
iat: 1704825600, // Issued at (timestamp)
exp: 1704826500, // Expires at (timestamp)
iss: "turbotrades", // Issuer
aud: "turbotrades-api" // Audience
}
```
---
## 🔍 How to Access Token Data
### Frontend (Browser)
The tokens are in httpOnly cookies, so JavaScript can't read them directly. But you can:
#### Option 1: Decode from API Response
```javascript
// After login or on page load, call this endpoint
const response = await fetch('/auth/decode-token', {
credentials: 'include' // Send cookies
});
const data = await response.json();
console.log(data.decoded);
// {
// userId: "...",
// steamId: "...",
// username: "YourName",
// avatar: "https://...",
// staffLevel: 0,
// ...
// }
```
#### Option 2: Get from /auth/me Endpoint
```javascript
const response = await fetch('/auth/me', {
credentials: 'include'
});
const data = await response.json();
console.log(data.user);
// Full user object from database
```
### Backend (Server-Side)
When you use the `authenticate` middleware, the decoded token data is available:
```javascript
import { authenticate } from './middleware/auth.js';
fastify.get('/protected', {
preHandler: authenticate
}, async (request, reply) => {
// Full user object from database
console.log(request.user.username);
console.log(request.user.avatar);
return { message: `Hello ${request.user.username}!` };
});
```
---
## 🎨 Frontend Usage Examples
### React Component
```javascript
import { useState, useEffect } from 'react';
function UserProfile() {
const [user, setUser] = useState(null);
useEffect(() => {
// Get user data from token
fetch('/auth/decode-token', { credentials: 'include' })
.then(res => res.json())
.then(data => {
if (data.success) {
setUser(data.decoded);
}
});
}, []);
if (!user) return <div>Loading...</div>;
return (
<div>
<img src={user.avatar} alt={user.username} />
<h1>{user.username}</h1>
<p>Steam ID: {user.steamId}</p>
{user.staffLevel > 0 && <span>Staff</span>}
</div>
);
}
```
### Vue Component
```vue
<template>
<div v-if="user">
<img :src="user.avatar" :alt="user.username" />
<h1>{{ user.username }}</h1>
<p>Steam ID: {{ user.steamId }}</p>
<span v-if="user.staffLevel > 0">Staff</span>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
const user = ref(null);
onMounted(async () => {
const response = await fetch('/auth/decode-token', {
credentials: 'include'
});
const data = await response.json();
if (data.success) {
user.value = data.decoded;
}
});
</script>
```
### Vanilla JavaScript
```javascript
// Get user info on page load
async function loadUserInfo() {
try {
const response = await fetch('/auth/decode-token', {
credentials: 'include'
});
const data = await response.json();
if (data.success) {
const user = data.decoded;
// Update UI
document.getElementById('username').textContent = user.username;
document.getElementById('avatar').src = user.avatar;
// Store in memory if needed
window.currentUser = user;
}
} catch (error) {
console.error('Failed to load user:', error);
}
}
loadUserInfo();
```
---
## 🔐 Security Notes
### Why httpOnly Cookies?
**Prevents XSS attacks** - JavaScript can't access the token
**Automatic sending** - Browser sends cookies automatically
**Secure storage** - Tokens stored securely by browser
### Token Lifetimes
- **Access Token:** 15 minutes (short-lived for security)
- **Refresh Token:** 7 days (for convenience)
When access token expires:
1. Frontend gets 401 error with "TokenExpired"
2. Call `/auth/refresh` to get new tokens
3. Retry the original request
---
## 📡 API Endpoints Reference
### Check Token Contents
```bash
GET /auth/decode-token
# With cookie (automatic in browser)
curl http://localhost:3000/auth/decode-token \
--cookie "accessToken=YOUR_TOKEN"
# Response:
{
"success": true,
"decoded": {
"userId": "...",
"steamId": "...",
"username": "...",
"avatar": "...",
"staffLevel": 0,
"iat": 1704825600,
"exp": 1704826500
}
}
```
### Get Full User Profile
```bash
GET /auth/me
curl http://localhost:3000/auth/me \
--cookie "accessToken=YOUR_TOKEN"
# Response:
{
"success": true,
"user": {
"_id": "...",
"username": "...",
"steamId": "...",
"avatar": "...",
"balance": 0,
"email": {...},
"staffLevel": 0,
...
}
}
```
### Refresh Tokens
```bash
POST /auth/refresh
curl -X POST http://localhost:3000/auth/refresh \
--cookie "refreshToken=YOUR_REFRESH_TOKEN"
# Response:
{
"success": true,
"message": "Tokens refreshed successfully",
"accessToken": "new-token",
"refreshToken": "new-refresh-token"
}
```
---
## 💡 Best Practices
### ✅ DO
- Store tokens in httpOnly cookies (already done)
- Use `/auth/decode-token` to get user info for UI
- Implement automatic token refresh on 401 errors
- Clear tokens on logout
- Use HTTPS in production
### ❌ DON'T
- Don't store tokens in localStorage (XSS vulnerable)
- Don't store sensitive data in tokens (keep them small)
- Don't decode tokens client-side if httpOnly (you can't)
- Don't use long-lived access tokens
---
## 🔄 Token Refresh Flow
```javascript
async function fetchWithAuth(url, options = {}) {
// First attempt with existing token
let response = await fetch(url, {
...options,
credentials: 'include'
});
// If token expired, refresh and retry
if (response.status === 401) {
const error = await response.json();
if (error.error === 'TokenExpired') {
// Refresh tokens
const refreshResponse = await fetch('/auth/refresh', {
method: 'POST',
credentials: 'include'
});
if (refreshResponse.ok) {
// Retry original request
response = await fetch(url, {
...options,
credentials: 'include'
});
} else {
// Refresh failed, redirect to login
window.location.href = '/login';
}
}
}
return response;
}
// Usage
const data = await fetchWithAuth('/user/profile');
```
---
## 📊 Token Size Comparison
**Before (without username/avatar):**
- Token size: ~200 bytes
- Needs database call to get name/avatar
**After (with username/avatar):**
- Token size: ~350 bytes
- No database call needed for basic info
- **Still well within JWT size limits (8KB)**
---
## 🎯 Summary
**What's in the token:**
- ✅ User ID (database reference)
- ✅ Steam ID (for Steam API calls)
- ✅ Username (display name)
- ✅ Avatar URL (profile picture)
- ✅ Staff Level (permissions)
**How to use it:**
- Frontend: Call `/auth/decode-token` or `/auth/me`
- Backend: Access via `request.user` (after authenticate middleware)
- Automatic: Cookies sent with every request
**Benefits:**
- No database calls for basic user info
- Faster UI rendering
- Self-contained authentication
- Stateless (can scale horizontally)
---
**Your JWT tokens now include everything needed for displaying user information! 🎉**