Files
dotcom-arcade/docs/10-features/01-authentication-authorization.md
Darkraise c12255c3c6 docs(auth): document guest play and launch permission
Guests now hold games.play when guest access is enabled, and
GET /api/games/:id/launch requires games.play.
2026-09-28 12:33:58 +07:00

9.5 KiB

Authentication & Authorization

Overview

JWT-based authentication system with Role-Based Access Control (RBAC). Enables secure user registration, login, session management, and fine-grained permission control across the application.

Architecture

Backend Components:

  • AuthService: Core authentication logic
  • Auth routes (routes/auth.ts): REST API endpoints
  • JWT utilities (utils/jwt.ts): Token generation and verification
  • Password utilities (utils/password.ts): Bcrypt hashing
  • RBAC middleware (middleware/rbac.ts): Permission checking
  • Auth middleware (middleware/auth.ts): JWT validation

Frontend Components:

  • AuthContext: React context for auth state
  • LoginForm: Login UI component
  • RegisterForm: Registration UI component
  • ProtectedRoute: Route guard component
  • useAuth hook: Authentication operations
  • Auth store (store/auth.ts): Zustand state management

Database Schema

users table:

CREATE TABLE users (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  username TEXT NOT NULL UNIQUE COLLATE NOCASE,
  email TEXT NOT NULL UNIQUE COLLATE NOCASE,
  password_hash TEXT NOT NULL,
  role_id INTEGER NOT NULL,
  is_active BOOLEAN DEFAULT 1,
  created_at TEXT DEFAULT (datetime('now')),
  updated_at TEXT DEFAULT (datetime('now')),
  last_login_at TEXT,
  FOREIGN KEY (role_id) REFERENCES roles(id)
);

refresh_tokens table:

CREATE TABLE refresh_tokens (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  user_id INTEGER NOT NULL,
  token TEXT NOT NULL UNIQUE,
  expires_at TEXT NOT NULL,
  created_at TEXT DEFAULT (datetime('now')),
  revoked_at TEXT,
  FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

login_attempts table:

CREATE TABLE login_attempts (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  username TEXT NOT NULL,
  ip_address TEXT NOT NULL,
  success BOOLEAN DEFAULT 0,
  attempted_at TEXT DEFAULT (datetime('now'))
);

API Endpoints

POST /api/auth/register

Register a new user account.

  • Body: { "username", "email", "password" }
  • Response: User object + tokens (201)
  • Validation: username 3-50 chars, email valid format, password min 6 chars
  • Errors: 400 validation, 403 registration disabled, 409 duplicate username/email

POST /api/auth/login

Authenticate with username and password.

  • Body: { "username", "password" }
  • Response: User object + tokens
  • Rate Limiting: 5 failed attempts locks account for 15 minutes
  • Errors: 401 invalid credentials, 429 too many attempts

POST /api/auth/logout

Revoke refresh token and logout.

  • Body: { "refreshToken" }
  • Response: { "success": true }

POST /api/auth/refresh

Refresh access token using refresh token.

  • Body: { "refreshToken" }
  • Response: New access token + new refresh token
  • Note: Old refresh token automatically revoked

GET /api/auth/me

Get current authenticated user information.

  • Headers: Authorization: Bearer {accessToken}
  • Response: User object with permissions

Authentication Flow

  1. Registration:

    • Validate input (unique username/email, password strength)
    • Hash password with bcrypt (10 rounds)
    • Create user record with default "user" role
    • Generate tokens
    • Log activity
  2. Login:

    • Check login attempts and lockout status
    • Fetch user (case-insensitive username)
    • Verify password against bcrypt hash
    • Record login attempt
    • Update last_login_at
    • Load user permissions from role
    • Generate tokens
  3. Token Validation:

    • Extract JWT from Authorization header
    • Verify token signature and expiration
    • Decode token payload
    • Fetch user with current data
    • Load and attach permissions
  4. Permission Checking:

    • RBAC middleware checks user permissions
    • User must have at least one required permission
    • 403 Forbidden if unauthorized
  5. Token Refresh:

    • Validate refresh token (not revoked, not expired)
    • Generate new access token
    • Generate new refresh token (rotation)
    • Revoke old refresh token automatically

Default Roles

Admin (priority: 100):

  • All permissions granted
  • Can manage users, roles, and settings

User (priority: 50):

  • games.read, games.play, games.download
  • playlists.read, .create, .update, .delete
  • Cannot manage users, roles, settings, or view activity logs

Guest (priority: 0):

  • games.read, playlists.read, games.play
  • Can browse and play games; no write access, and plays are not tracked

Permissions

Complete permission list in Role-Based Access Control

Configuration

Environment Variables (.env):

JWT_SECRET=your-secret-key-here        # Change in production
JWT_EXPIRES_IN=1h                       # Access token expiration
REFRESH_TOKEN_EXPIRES_IN=30d            # Refresh token expiration (cookie TTL)
DOMAIN=http://localhost:5173           # Frontend URL

Cookie Configuration (Backend):

  • Cookie name: fp_refresh
  • Path: /api/auth (scoped to auth routes only)
  • Max-Age: 30 days (same as REFRESH_TOKEN_EXPIRES_IN)
  • Secure: true in production (HTTPS only)
  • HttpOnly: true (prevents JavaScript access)
  • SameSite: lax (CSRF protection)

Auth Settings (Database):

  • auth.guestAccessEnabled - Allow guest browsing (default: true)
  • auth.userRegistrationEnabled - Allow new user registration (default: true)
  • auth.maxLoginAttempts - Before lockout (default: 5)
  • auth.lockoutDurationMinutes - Lockout duration (default: 15)

Security Features

Password Security:

  • Bcrypt hashing with 10 salt rounds
  • Minimum 6 character requirement
  • Password not returned in API responses

Token Security:

  • Access tokens: 1 hour expiry, stored in memory only
  • Refresh tokens: 30 days expiry, stored in HTTP-only cookies
  • Tokens use HS256 algorithm
  • Automatic token refresh via cookie on page reload
  • Automatic refresh token revocation on refresh
  • Bulk token revocation available (admin)

Cookie Security:

  • Refresh token cookies protected from XSS via HttpOnly flag
  • CSRF protection via SameSite=lax
  • Cookie scoped to /api/auth path (prevents leakage to other routes)
  • Secure flag enforced in production (HTTPS only)

Rate Limiting:

  • 5 failed login attempts before lockout
  • 15 minute lockout duration
  • Tracking by both username and IP address
  • Old login attempts cleaned up after 24 hours

Session Security:

  • IP address and user agent tracked per session
  • Activity logging for audit trail
  • Refresh tokens stored in HTTP-only cookies (XSS-resistant)
  • Access tokens stored in memory only (lost on page reload)
  • Session recovery via cookie on page reload
  • HTTPS required in production (for secure cookie flag)

Shared Access Validation

Guest users (id=0) are explicitly excluded from shared access bypass checks. The validateSharedGameAccess middleware verifies req.user.id !== 0 before granting access, preventing unauthenticated users from accessing shared resources meant for specific authenticated users.

Session Recovery & Permission Refresh

After token refresh (including on page reload), the frontend calls GET /api/auth/me to refresh user permissions from the server. This ensures permissions are always server-authoritative and prevents stale cached permissions from granting inappropriate access.

Common Use Cases

1. User Registration

const registerMutation = useRegister();

await registerMutation.mutateAsync({
  username: 'johndoe',
  email: 'john@example.com',
  password: 'secure123',
});

2. User Login

const loginMutation = useLogin();

await loginMutation.mutateAsync({
  username: 'johndoe',
  password: 'secure123',
});

3. Guest Access

const { loginAsGuest } = useAuth();

loginAsGuest();

4. Protected Route

<Route
  path="/admin/users"
  element={
    <ProtectedRoute requiredPermissions={['users.read']}>
      <UsersView />
    </ProtectedRoute>
  }
/>

5. Permission-Based UI

const { hasPermission } = useAuth();

{
  hasPermission('users.create') && (
    <Button onClick={handleCreateUser}>Create User</Button>
  );
}

6. Change Password

const mutation = useChangePassword();

await mutation.mutateAsync({
  userId: currentUser.id,
  currentPassword: 'old123',
  newPassword: 'new456',
});

Best Practices

  1. Always use HTTPS in production - Required for secure cookie flag
  2. Change JWT_SECRET from default in production
  3. Token rotation on refresh is automatic (old token revoked)
  4. Monitor login attempts for suspicious activity
  5. Regular cleanup of expired tokens and old login attempts
  6. Use strong passwords
  7. Enable email verification for production
  8. Audit activity logs regularly
  9. Frontend must use withCredentials: true on axios client
  10. Frontend must call /api/auth/refresh on app initialization for session recovery

Troubleshooting

"Invalid credentials" error:

  • Verify username and password are correct
  • Check if account is active (is_active = 1)
  • Ensure password was hashed correctly during registration

"Too many login attempts" error:

  • Wait for lockout duration to expire (15 minutes default)
  • Or manually clear login attempts from database
  • Check if IP address or username is being rate-limited

Token expired errors:

  • Implement automatic token refresh on client
  • Check system clock synchronization
  • Verify JWT expiration times in configuration

Permission denied errors:

  • Verify user has correct role assigned
  • Check role-permission mappings in database
  • Ensure permissions loaded correctly in token