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

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:

  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

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-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

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! 🎉