Skip to main content

JWT Authentication

The JWT Authentication system enables secure player access to tournament games through cryptographically signed tokens. Game Platform validates these tokens to authenticate players and authorize their participation in tournaments.

JWT Token Overview

JWT tokens are issued by the Tournament System when players register for tournaments and contain all necessary information for game platform authentication and authorization.

Token Structure

JWT tokens follow the standard format: header.payload.signature Example Token:
eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJ0b3VybmFtZW50SWQiOiIxMjM0NSIsInBsYXllcklkIjoidHBfcGxheWVyXzQ1NiIsInJvb21JZCI6InJvb21fYWJjMTIzIiwiZ2FtZVNsdWciOiJjcmFzaC1jbGFzc2ljIiwicGVybWlzc2lvbnMiOlsicGxheSIsInJlYnV5Il0sImlhdCI6MTY0MjA4OTAwMCwiZXhwIjoxNjQyMDkyNjAwfQ.signature

Token Payload

{
  "tournamentId": "12345",
  "playerId": "tp_player_456",
  "roomId": "room_abc123",
  "gameSlug": "crash-classic",
  "permissions": ["play", "rebuy", "chat"],
  "operatorId": "op_1234567890abcdef",
  "playerInfo": {
    "displayName": "PlayerName",
    "entryCount": 2,
    "totalPaid": 20.00,
    "vipLevel": "gold"
  },
  "iat": 1642089000,
  "exp": 1642092600
}

Token Claims

ClaimTypeDescription
tournamentIdStringTournament identifier
playerIdStringTournament-specific player ID
roomIdStringGame room identifier
gameSlugStringGame type identifier
permissionsArrayAllowed actions (play, rebuy, chat, spectate)
operatorIdStringOperator identifier for multi-tenant isolation
playerInfoObjectPlayer metadata and statistics
iatNumberToken issued at timestamp
expNumberToken expiration timestamp

Token Validation API

Validate JWT Token

Validate player JWT tokens for game access authorization:
POST /internal/auth/validate-token
Authorization: Bearer {internal-api-key}
Content-Type: application/json

{
  "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9..."
}
Response (Valid Token):
{
  "valid": true,
  "tournamentId": "12345",
  "playerId": "tp_player_456",
  "roomId": "room_abc123",
  "gameSlug": "crash-classic",
  "permissions": ["play", "rebuy", "chat"],
  "operatorId": "op_1234567890abcdef",
  "playerInfo": {
    "displayName": "PlayerName",
    "entryCount": 2,
    "totalPaid": 20.00,
    "currentPosition": 15,
    "points": 1250,
    "status": "active",
    "vipLevel": "gold",
    "preferredLanguage": "en"
  },
  "tournamentInfo": {
    "name": "Daily Championship",
    "status": "in_progress",
    "currentRound": 3,
    "totalRounds": 5,
    "startTime": "2024-01-15T20:00:00Z"
  },
  "expiresAt": "2024-01-15T21:30:00Z",
  "remainingTime": 3420
}
Response (Invalid Token):
{
  "valid": false,
  "error": "token_expired",
  "message": "JWT token has expired",
  "expiredAt": "2024-01-15T21:30:00Z"
}

Token Validation Errors

Error CodeDescriptionAction Required
token_expiredToken past expiration timeRequest new token from Casino System
token_malformedInvalid token formatCheck token format and encoding
signature_invalidCryptographic signature failedToken may be tampered - reject access
tournament_not_foundTournament doesn’t existVerify tournament is still active
player_eliminatedPlayer eliminated from tournamentAllow spectate mode only
insufficient_permissionsToken lacks required permissionsCheck permission requirements

Authentication Flow

Player Connection Process

Implementation Examples

JavaScript Token Validation

class TournamentAuth {
  constructor(internalApiKey) {
    this.apiKey = internalApiKey;
    this.baseUrl = 'https://tournament-system.internal';
  }
  
  async validateToken(jwtToken) {
    try {
      const response = await fetch(`${this.baseUrl}/internal/auth/validate-token`, {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${this.apiKey}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ token: jwtToken })
      });
      
      if (!response.ok) {
        throw new Error(`Validation failed: ${response.status}`);
      }
      
      const result = await response.json();
      
      if (!result.valid) {
        console.warn('Token validation failed:', result.error);
        return null;
      }
      
      return {
        tournamentId: result.tournamentId,
        playerId: result.playerId,
        roomId: result.roomId,
        permissions: result.permissions,
        playerInfo: result.playerInfo,
        expiresAt: new Date(result.expiresAt)
      };
      
    } catch (error) {
      console.error('Token validation error:', error);
      return null;
    }
  }
  
  async authenticatePlayer(jwtToken) {
    const validation = await this.validateToken(jwtToken);
    
    if (!validation) {
      throw new Error('Authentication failed - invalid token');
    }
    
    // Check if player has required permissions
    if (!validation.permissions.includes('play')) {
      throw new Error('Authentication failed - insufficient permissions');
    }
    
    // Check token expiration
    if (validation.expiresAt <= new Date()) {
      throw new Error('Authentication failed - token expired');
    }
    
    console.log(`Player ${validation.playerInfo.displayName} authenticated for tournament ${validation.tournamentId}`);
    
    return validation;
  }
}

// Usage example
const auth = new TournamentAuth('internal-api-key');

// Authenticate player on connection
const authenticatePlayerConnection = async (jwtToken) => {
  try {
    const playerAuth = await auth.authenticatePlayer(jwtToken);
    
    // Player authenticated successfully
    return {
      success: true,
      playerId: playerAuth.playerId,
      tournamentId: playerAuth.tournamentId,
      roomId: playerAuth.roomId,
      playerInfo: playerAuth.playerInfo,
      permissions: playerAuth.permissions
    };
    
  } catch (error) {
    console.error('Player authentication failed:', error.message);
    
    return {
      success: false,
      error: error.message
    };
  }
};

Node.js Middleware Example

// Express.js middleware for JWT authentication
const authenticatePlayer = async (req, res, next) => {
  try {
    // Extract JWT from Authorization header or query parameter
    const token = req.headers.authorization?.replace('Bearer ', '') || req.query.token;
    
    if (!token) {
      return res.status(401).json({
        error: 'missing_token',
        message: 'JWT token is required'
      });
    }
    
    // Validate token with Tournament System
    const validation = await auth.validateToken(token);
    
    if (!validation) {
      return res.status(401).json({
        error: 'invalid_token',
        message: 'JWT token validation failed'
      });
    }
    
    // Add player info to request object
    req.player = {
      tournamentId: validation.tournamentId,
      playerId: validation.playerId,
      roomId: validation.roomId,
      permissions: validation.permissions,
      info: validation.playerInfo
    };
    
    // Check tournament-specific permissions
    if (req.path.includes('/rebuy') && !validation.permissions.includes('rebuy')) {
      return res.status(403).json({
        error: 'insufficient_permissions',
        message: 'Rebuy permission required'
      });
    }
    
    next();
    
  } catch (error) {
    console.error('Authentication middleware error:', error);
    
    return res.status(500).json({
      error: 'authentication_error',
      message: 'Authentication system error'
    });
  }
};

// Usage in routes
app.get('/game/tournament/:id/join', authenticatePlayer, (req, res) => {
  // Player is authenticated, req.player contains player info
  const { tournamentId, playerId, roomId } = req.player;
  
  res.json({
    message: 'Game joined successfully',
    tournamentId: tournamentId,
    playerId: playerId,
    roomId: roomId
  });
});

Token Refresh & Expiration

Token Expiration Handling

JWT tokens have limited validity periods. Handle expiration gracefully:
class TokenManager {
  constructor(auth) {
    this.auth = auth;
    this.tokenCache = new Map();
    this.refreshThreshold = 5 * 60 * 1000; // 5 minutes before expiration
  }
  
  async getValidToken(playerId, currentToken) {
    const cached = this.tokenCache.get(playerId);
    
    // Check if cached token is still valid
    if (cached && cached.expiresAt > new Date(Date.now() + this.refreshThreshold)) {
      return cached.token;
    }
    
    // Validate current token
    const validation = await this.auth.validateToken(currentToken);
    
    if (validation && validation.expiresAt > new Date(Date.now() + this.refreshThreshold)) {
      // Token is still valid, cache it
      this.tokenCache.set(playerId, {
        token: currentToken,
        expiresAt: validation.expiresAt
      });
      
      return currentToken;
    }
    
    // Token expired or expiring soon - request refresh
    return await this.requestTokenRefresh(playerId);
  }
  
  async requestTokenRefresh(playerId) {
    try {
      // Request new token from Casino System
      // This would typically involve redirecting player back to casino
      // or making an API call if refresh tokens are supported
      
      console.log(`Token refresh required for player ${playerId}`);
      
      // For now, indicate token refresh needed
      throw new Error('TOKEN_REFRESH_REQUIRED');
      
    } catch (error) {
      console.error('Token refresh failed:', error);
      throw error;
    }
  }
}

Automatic Token Validation

// WebSocket connection with automatic token validation
class TournamentSocket {
  constructor(wsUrl, jwtToken) {
    this.wsUrl = wsUrl;
    this.jwtToken = jwtToken;
    this.auth = new TournamentAuth('internal-api-key');
    this.reconnectAttempts = 0;
    this.maxReconnectAttempts = 5;
  }
  
  async connect() {
    try {
      // Validate token before connecting
      const validation = await this.auth.validateToken(this.jwtToken);
      
      if (!validation) {
        throw new Error('Invalid JWT token');
      }
      
      // Connect to WebSocket with validated token
      this.ws = new WebSocket(`${this.wsUrl}?token=${this.jwtToken}`);
      
      this.ws.onopen = () => {
        console.log('Tournament WebSocket connected');
        this.reconnectAttempts = 0;
      };
      
      this.ws.onmessage = (event) => {
        this.handleMessage(JSON.parse(event.data));
      };
      
      this.ws.onclose = (event) => {
        console.log('Tournament WebSocket closed:', event.code, event.reason);
        
        // Handle different close codes
        if (event.code === 1008) { // Policy violation (auth failure)
          console.error('Authentication failed - token may be invalid');
          this.handleAuthFailure();
        } else {
          this.attemptReconnect();
        }
      };
      
      this.ws.onerror = (error) => {
        console.error('Tournament WebSocket error:', error);
      };
      
    } catch (error) {
      console.error('Tournament connection failed:', error);
      throw error;
    }
  }
  
  handleAuthFailure() {
    // Redirect back to casino for new token
    window.location.href = '/casino/tournament-login?expired=true';
  }
  
  attemptReconnect() {
    if (this.reconnectAttempts < this.maxReconnectAttempts) {
      this.reconnectAttempts++;
      
      const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);
      console.log(`Attempting reconnect in ${delay}ms (attempt ${this.reconnectAttempts})`);
      
      setTimeout(() => {
        this.connect();
      }, delay);
    } else {
      console.error('Max reconnection attempts exceeded');
      this.handleAuthFailure();
    }
  }
}

Security Considerations

Token Security Best Practices

Signature Verification

Always verify JWT signatures using EdDSA algorithm with proper key validation

Expiration Checking

Validate token expiration times and reject expired tokens immediately

Permission Validation

Check token permissions match required actions before allowing access

Secure Transmission

Only transmit tokens over HTTPS/WSS encrypted connections

Common Security Issues

Never trust client-side token validation - always validate tokens server-side via the internal API. Client-side validation should only be used for UX improvements.

Token Tampering Detection

// Always validate tokens server-side
const validateClientToken = async (clientToken) => {
  // DON'T do client-side validation only
  // const payload = JSON.parse(atob(clientToken.split('.')[1])); // INSECURE
  
  // DO validate via internal API
  const validation = await auth.validateToken(clientToken);
  
  if (!validation) {
    throw new Error('Token validation failed');
  }
  
  return validation;
};

Replay Attack Prevention

// Track token usage to prevent replay attacks
class TokenTracker {
  constructor() {
    this.usedTokens = new Set();
    this.cleanupInterval = setInterval(() => {
      this.cleanup();
    }, 60000); // Cleanup every minute
  }
  
  isTokenUsed(tokenId) {
    return this.usedTokens.has(tokenId);
  }
  
  markTokenUsed(tokenId, expiresAt) {
    this.usedTokens.add({
      id: tokenId,
      expiresAt: expiresAt
    });
  }
  
  cleanup() {
    const now = new Date();
    this.usedTokens = new Set([...this.usedTokens].filter(
      token => token.expiresAt > now
    ));
  }
}

Next Steps