Guests now hold games.play when guest access is enabled, and GET /api/games/:id/launch requires games.play.
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 stateLoginForm: Login UI componentRegisterForm: Registration UI componentProtectedRoute: Route guard componentuseAuthhook: 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
-
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
-
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
-
Token Validation:
- Extract JWT from Authorization header
- Verify token signature and expiration
- Decode token payload
- Fetch user with current data
- Load and attach permissions
-
Permission Checking:
- RBAC middleware checks user permissions
- User must have at least one required permission
- 403 Forbidden if unauthorized
-
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
- Always use HTTPS in production - Required for secure cookie flag
- Change JWT_SECRET from default in production
- Token rotation on refresh is automatic (old token revoked)
- Monitor login attempts for suspicious activity
- Regular cleanup of expired tokens and old login attempts
- Use strong passwords
- Enable email verification for production
- Audit activity logs regularly
- Frontend must use
withCredentials: trueon axios client - Frontend must call
/api/auth/refreshon 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