7.8 KiB
7.8 KiB
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:
{
// 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
// 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
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:
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
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
<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
// 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:
- Frontend gets 401 error with "TokenExpired"
- Call
/auth/refreshto get new tokens - Retry the original request
📡 API Endpoints Reference
Check Token Contents
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
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
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-tokento 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
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-tokenor/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! 🎉