Files

9.1 KiB

JWT Authentication Architecture

Last Updated: January 28, 2026
Status: Implemented (Step 9 Complete)

Overview

Pokedex.Online uses JWT (JSON Web Token) authentication for admin access to protected features like the Gamemaster Manager. The system provides:

  • Token-based authentication (stateless)
  • 7-day token expiration with refresh capability
  • Permission-based access control
  • Secure localStorage token storage
  • Automatic Bearer token injection in API requests

Architecture Diagram

┌─────────────────────────────────────────────────────────────┐
│ Client (Browser)                                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  AdminLogin View                                           │
│  ↓ (password)                                             │
│  useAuth Composable (login → POST /auth/login)            │
│  ↓ (token received)                                       │
│  localStorage.setItem('auth_token', token)                │
│  ↓                                                        │
│  api-client.setDefaultHeader('Authorization', Bearer ...) │
│  ↓                                                        │
│  Router Guards (requiresAdmin check)                      │
│  ↓                                                        │
│  Protected Routes (GamemasterManager, etc.)               │
│                                                            │
└─────────────────────────────────────────────────────────────┘
                              ↕
┌─────────────────────────────────────────────────────────────┐
│ Server (Express)                                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Auth Routes (server/routes/auth.js)                       │
│  ↓ /auth/login (POST) → verifyPassword                    │
│  ↓ createToken(payload, secret, expiration)              │
│  ↓ return { token, user, expiresIn }                      │
│                                                            │
│  Auth Middleware (server/middleware/auth.js)              │
│  ↓ authMiddleware() → verifyToken(token)                  │
│  ↓ requirePermission() → check permissions                │
│  ↓ requireAdmin() → check isAdmin flag                    │
│                                                            │
│  Protected Routes                                          │
│  ↓ /api/gamemaster/* (with @authMiddleware)              │
│  ↓ /api/feature-flags/* (with @requirePermission)        │
│                                                            │
└─────────────────────────────────────────────────────────────┘

File Structure

Frontend:
├── src/
│   ├── composables/
│   │   └── useAuth.js                 # Auth state management
│   ├── views/
│   │   └── AdminLogin.vue             # Login page
│   ├── router/
│   │   └── guards.js                  # Route protection
│   └── utilities/
│       └── api-client.js              # Enhanced with header mgmt
│
Backend:
├── server/
│   ├── utils/
│   │   └── jwt-utils.js               # Token creation/validation
│   ├── middleware/
│   │   └── auth.js                    # Auth middleware
│   └── routes/
│       └── auth.js                    # Auth endpoints
│
Tests:
└── tests/unit/
    ├── composables/
    │   └── useAuth.test.js            # Auth composable tests
    └── views/
        └── AdminLogin.test.js         # Login page tests

API Endpoints

POST /auth/login

Authenticate with password to receive JWT token.

Request:

{
  "password": "admin-password"
}

Response (Success):

{
  "success": true,
  "token": "eyJhbGc...",
  "expiresIn": 604800,
  "user": {
    "isAdmin": true,
    "permissions": ["admin", "gamemaster-edit"]
  }
}

Response (Error):

{
  "error": "Invalid password",
  "code": "INVALID_PASSWORD"
}

POST /auth/verify

Verify that a token is valid and get its metadata.

Request:

{
  "token": "eyJhbGc..."
}

Response:

{
  "valid": true,
  "user": {
    "isAdmin": true,
    "permissions": ["admin", "gamemaster-edit"]
  },
  "expiresIn": 3600,
  "expiresAt": "2026-02-04T17:48:00Z"
}

POST /auth/refresh

Extend token expiration.

Request:

{
  "token": "eyJhbGc..."
}

Response:

{
  "success": true,
  "token": "eyJhbGc...",
  "expiresIn": 604800
}

GET /auth/user

Get current user information (requires valid token).

Headers:

Authorization: Bearer eyJhbGc...

Response:

{
  "user": {
    "isAdmin": true,
    "permissions": ["admin", "gamemaster-edit"],
    "loginTime": "2026-01-28T17:48:00Z"
  }
}

POST /auth/logout

Logout endpoint (token invalidation happens client-side).

Response:

{
  "success": true,
  "message": "Logged out successfully"
}

Frontend Integration

Using useAuth in Components

import { useAuth } from '../composables/useAuth.js';

export default {
  setup() {
    const { 
      login,           // login(password)
      logout,          // logout()
      token,           // Reactive token ref
      user,            // Reactive user ref
      isAuthenticated, // Boolean computed
      isAdmin,         // Boolean computed
      hasPermission    // (perm) => boolean
    } = useAuth();

    return {
      login,
      logout,
      token,
      user,
      isAuthenticated,
      isAdmin,
      hasPermission
    };
  }
};

Protected Routes

import { setupAuthGuards } from './router/guards.js';

const router = createRouter({ ... });

setupAuthGuards(router);

// In route definitions:
{
  path: '/gamemaster-manager',
  component: GamemasterManager,
  meta: { requiresAdmin: true }  // Protected route
}

Setting Up Auth on App Startup

import { useAuth } from './composables/useAuth.js';

export default {
  async setup() {
    const { initializeAuth, setupAuthInterceptor } = useAuth();
    
    // Initialize from localStorage
    await initializeAuth();
    
    // Set up automatic token injection
    setupAuthInterceptor();
  }
};

Security Considerations

Implemented

  • JWT tokens with HMAC-SHA256 signature
  • 7-day expiration with refresh capability
  • Bearer token in Authorization header
  • Token stored in localStorage (browser-side)
  • Permission-based access control
  • Server-side token validation

⚠️ Recommendations

For production deployment:

  1. HTTPS Only: Ensure all authentication over HTTPS
  2. Environment Secrets: Store JWT_SECRET in environment variables
  3. Password Hashing: Use bcrypt for password hashing on server
  4. CSRF Protection: Add CSRF tokens for state-changing requests
  5. Rate Limiting: Implement rate limiting on /auth/login endpoint
  6. Secure Cookies: Consider httpOnly cookies for sensitive tokens
  7. Token Rotation: Implement token rotation for long-lived sessions
  8. Logout Blacklist: Maintain blacklist of revoked tokens (if needed)

Current Limitations

  • Passwords stored in environment/config (suitable for single admin scenario)
  • No multi-user support yet (planned for future)
  • Tokens valid until expiration (no immediate revocation)
  • Single secret key (key rotation not yet implemented)

Testing

Tests verify:

useAuth composable state management
AdminLogin component rendering and interaction
Login/logout flow
Token state in localStorage
Permission checking logic
Router guard integration

Run tests:

npm test                  # Watch mode
npm test -- AdminLogin   # Specific test
npm run test:coverage    # Coverage report

Next Steps (Phase 3: Steps 10-12)

  • Feature flags system leveraging JWT permissions
  • Secure backend configuration management
  • Environment-based flag toggleput
  • Flag caching and invalidation
  • Admin panel for flag management

Related Documentation: