db = $class_database ?: new VDatabase(); $this->logger = VLogger::getInstance(); $this->security = VSecurity::getInstance(); $this->redis = $this->getRedisConnection(); // Configure secure session settings $this->configureSession(); } /** * Configure secure session settings */ private function configureSession() { if (session_status() === PHP_SESSION_NONE) { // Secure session configuration ini_set('session.cookie_httponly', 1); ini_set('session.cookie_secure', isset($_SERVER['HTTPS']) ? 1 : 0); ini_set('session.cookie_samesite', 'Strict'); ini_set('session.use_strict_mode', 1); ini_set('session.cookie_lifetime', 0); // Session cookies only // Use Redis for session storage if available if ($this->redis) { ini_set('session.save_handler', 'redis'); $redisHost = getenv('REDIS_HOST') ?: 'redis'; $redisPort = getenv('REDIS_PORT') ?: 6379; $redisDb = getenv('REDIS_DB') ?: 0; ini_set('session.save_path', "tcp://{$redisHost}:{$redisPort}?database={$redisDb}"); } session_start(); } } /** * Get Redis connection * @return Redis|null */ private function getRedisConnection() { try { if (!class_exists('Redis')) { return null; } $redis = new Redis(); $host = getenv('REDIS_HOST') ?: 'redis'; $port = (int)(getenv('REDIS_PORT') ?: 6379); $db = (int)(getenv('REDIS_DB') ?: 0); if (!$redis->connect($host, $port, 2)) { return null; } if ($db > 0) { $redis->select($db); } return $redis; } catch (Exception $e) { $this->logger->warning('Redis connection failed for auth', ['error' => $e->getMessage()]); return null; } } /** * Register a new user with email verification * @param array $userData User registration data * @return array Result with success status and message */ public function register($userData) { try { // Validate required fields $requiredFields = ['username', 'email', 'password']; foreach ($requiredFields as $field) { if (empty($userData[$field])) { return ['success' => false, 'message' => "Field '{$field}' is required"]; } } // Sanitize and validate input $username = VSecurity::validateInput($userData['username'], 'alphanum', null, ['min_length' => 3, 'max_length' => 50]); $email = VSecurity::validateInput($userData['email'], 'email'); $password = $userData['password']; // Don't sanitize password if (!$username) { return ['success' => false, 'message' => 'Invalid username. Use 3-50 alphanumeric characters only.']; } if (!$email) { return ['success' => false, 'message' => 'Invalid email address']; } if (strlen($password) < 8) { return ['success' => false, 'message' => 'Password must be at least 8 characters long']; } // Check password strength if (!$this->isPasswordStrong($password)) { return ['success' => false, 'message' => 'Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character']; } // Check if username or email already exists if ($this->userExists($username, $email)) { $this->logger->logSecurityEvent('Registration attempt with existing credentials', [ 'username' => $username, 'email' => $email ]); return ['success' => false, 'message' => 'Username or email already exists']; } // Generate verification token $verificationToken = bin2hex(random_bytes(32)); // Hash password $passwordHash = password_hash($password, PASSWORD_DEFAULT); // Insert user into database $sql = "INSERT INTO `db_users` (`username`, `email`, `password_hash`, `role`, `status`, `email_verified`, `verification_token`, `created_at`) VALUES (?, ?, ?, 'member', 'active', 0, ?, NOW())"; $result = $this->db->dbConnection()->Execute($sql, [$username, $email, $passwordHash, $verificationToken]); if (!$result) { $this->logger->error('User registration failed', [ 'username' => $username, 'email' => $email, 'error' => $this->db->dbConnection()->ErrorMsg() ]); return ['success' => false, 'message' => 'Registration failed. Please try again.']; } $userId = $this->db->dbConnection()->Insert_ID(); // Send verification email $this->sendVerificationEmail($email, $username, $verificationToken); $this->logger->info('User registered successfully', [ 'user_id' => $userId, 'username' => $username, 'email' => $email ]); return [ 'success' => true, 'message' => 'Registration successful! Please check your email to verify your account.', 'user_id' => $userId ]; } catch (Exception $e) { $this->logger->error('Registration error', [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); return ['success' => false, 'message' => 'An error occurred during registration']; } } /** * Verify email address * @param string $token Verification token * @return array Result with success status and message */ public function verifyEmail($token) { try { $token = VSecurity::validateInput($token, 'alphanum'); if (!$token) { return ['success' => false, 'message' => 'Invalid verification token']; } $sql = "SELECT `user_id`, `username`, `email` FROM `db_users` WHERE `verification_token` = ? AND `email_verified` = 0"; $result = $this->db->dbConnection()->Execute($sql, [$token]); if (!$result || $result->EOF) { $this->logger->logSecurityEvent('Invalid email verification attempt', ['token' => $token]); return ['success' => false, 'message' => 'Invalid or expired verification token']; } $userData = $result->fields; // Update user as verified $updateSql = "UPDATE `db_users` SET `email_verified` = 1, `verification_token` = NULL, `updated_at` = NOW() WHERE `user_id` = ?"; $updateResult = $this->db->dbConnection()->Execute($updateSql, [$userData['user_id']]); if (!$updateResult) { return ['success' => false, 'message' => 'Verification failed. Please try again.']; } $this->logger->info('Email verified successfully', [ 'user_id' => $userData['user_id'], 'username' => $userData['username'], 'email' => $userData['email'] ]); return [ 'success' => true, 'message' => 'Email verified successfully! You can now log in.', 'user_id' => $userData['user_id'] ]; } catch (Exception $e) { $this->logger->error('Email verification error', [ 'error' => $e->getMessage(), 'token' => $token ?? 'unknown' ]); return ['success' => false, 'message' => 'An error occurred during verification']; } } /** * Authenticate user login * @param string $identifier Username or email * @param string $password Password * @param bool $rememberMe Remember me option * @return array Result with success status and user data */ public function login($identifier, $password, $rememberMe = false) { try { // Rate limiting for login attempts $clientIP = $this->getClientIP(); $rateLimitKey = "login_attempts_{$clientIP}"; if (!VSecurity::checkRateLimit($rateLimitKey, $this->maxLoginAttempts, $this->lockoutDuration, 'login')) { $this->logger->logSecurityEvent('Login rate limit exceeded', [ 'ip' => $clientIP, 'identifier' => $identifier ]); return ['success' => false, 'message' => 'Too many login attempts. Please try again later.']; } // Validate input $identifier = VSecurity::validateInput($identifier, 'string', null, ['max_length' => 255]); if (!$identifier || !$password) { return ['success' => false, 'message' => 'Username/email and password are required']; } // Find user by username or email $user = $this->findUserByIdentifier($identifier); if (!$user) { $this->logger->logSecurityEvent('Login attempt with non-existent user', [ 'identifier' => $identifier, 'ip' => $clientIP ]); return ['success' => false, 'message' => 'Invalid credentials']; } // Check if account is active if ($user['status'] !== 'active') { $this->logger->logSecurityEvent('Login attempt with inactive account', [ 'user_id' => $user['user_id'], 'username' => $user['username'], 'status' => $user['status'] ]); return ['success' => false, 'message' => 'Account is not active']; } // Verify password if (!password_verify($password, $user['password_hash'])) { $this->logger->logSecurityEvent('Failed login attempt', [ 'user_id' => $user['user_id'], 'username' => $user['username'], 'ip' => $clientIP ]); return ['success' => false, 'message' => 'Invalid credentials']; } // Check if email is verified (optional based on configuration) global $cfg; $requireEmailVerification = $cfg['require_email_verification'] ?? true; if ($requireEmailVerification && !$user['email_verified']) { return ['success' => false, 'message' => 'Please verify your email address before logging in']; } // Create session $sessionResult = $this->createSession($user, $rememberMe); if (!$sessionResult['success']) { return $sessionResult; } // Update last login $this->updateLastLogin($user['user_id']); $this->logger->info('User logged in successfully', [ 'user_id' => $user['user_id'], 'username' => $user['username'], 'ip' => $clientIP, 'remember_me' => $rememberMe ]); return [ 'success' => true, 'message' => 'Login successful', 'user' => [ 'user_id' => $user['user_id'], 'username' => $user['username'], 'email' => $user['email'], 'role' => $user['role'], 'email_verified' => $user['email_verified'] ] ]; } catch (Exception $e) { $this->logger->error('Login error', [ 'error' => $e->getMessage(), 'identifier' => $identifier ?? 'unknown' ]); return ['success' => false, 'message' => 'An error occurred during login']; } } /** * Create secure session * @param array $user User data * @param bool $rememberMe Remember me option * @return array Result with success status */ private function createSession($user, $rememberMe = false) { try { // Regenerate session ID to prevent session fixation session_regenerate_id(true); // Set session data $_SESSION['USER_ID'] = $user['user_id']; $_SESSION['USERNAME'] = $user['username']; $_SESSION['EMAIL'] = $user['email']; $_SESSION['ROLE'] = $user['role']; $_SESSION['LOGIN_TIME'] = time(); $_SESSION['LAST_ACTIVITY'] = time(); $_SESSION['IP_ADDRESS'] = $this->getClientIP(); $_SESSION['USER_AGENT'] = $_SERVER['HTTP_USER_AGENT'] ?? ''; $_SESSION['SESSION_TOKEN'] = bin2hex(random_bytes(32)); // Store session in database $sessionId = session_id(); $expiresAt = date('Y-m-d H:i:s', time() + ($rememberMe ? $this->rememberMeTimeout : $this->sessionTimeout)); $sql = "INSERT INTO `db_sessions` (`session_id`, `user_id`, `ip_address`, `user_agent`, `created_at`, `expires_at`, `remember_me`) VALUES (?, ?, ?, ?, NOW(), ?, ?) ON DUPLICATE KEY UPDATE `ip_address` = VALUES(`ip_address`), `user_agent` = VALUES(`user_agent`), `expires_at` = VALUES(`expires_at`), `remember_me` = VALUES(`remember_me`)"; $result = $this->db->dbConnection()->Execute($sql, [ $sessionId, $user['user_id'], $_SESSION['IP_ADDRESS'], $_SESSION['USER_AGENT'], $expiresAt, $rememberMe ? 1 : 0 ]); if (!$result) { $this->logger->error('Session creation failed', [ 'user_id' => $user['user_id'], 'error' => $this->db->dbConnection()->ErrorMsg() ]); return ['success' => false, 'message' => 'Session creation failed']; } // Set remember me cookie if requested if ($rememberMe) { $rememberToken = bin2hex(random_bytes(32)); $cookieExpiry = time() + $this->rememberMeTimeout; setcookie('remember_token', $rememberToken, $cookieExpiry, '/', '', isset($_SERVER['HTTPS']), true); // Store remember token in database $tokenSql = "UPDATE `db_users` SET `remember_token` = ?, `remember_expires` = ? WHERE `user_id` = ?"; $this->db->dbConnection()->Execute($tokenSql, [ password_hash($rememberToken, PASSWORD_DEFAULT), date('Y-m-d H:i:s', $cookieExpiry), $user['user_id'] ]); } return ['success' => true]; } catch (Exception $e) { $this->logger->error('Session creation error', [ 'error' => $e->getMessage(), 'user_id' => $user['user_id'] ?? 'unknown' ]); return ['success' => false, 'message' => 'Session creation failed']; } } /** * Logout user and destroy session * @return array Result with success status */ public function logout() { try { $userId = $_SESSION['USER_ID'] ?? null; $sessionId = session_id(); // Remove session from database if ($sessionId) { $sql = "DELETE FROM `db_sessions` WHERE `session_id` = ?"; $this->db->dbConnection()->Execute($sql, [$sessionId]); } // Clear remember me cookie and token if (isset($_COOKIE['remember_token'])) { setcookie('remember_token', '', time() - 3600, '/', '', isset($_SERVER['HTTPS']), true); if ($userId) { $tokenSql = "UPDATE `db_users` SET `remember_token` = NULL, `remember_expires` = NULL WHERE `user_id` = ?"; $this->db->dbConnection()->Execute($tokenSql, [$userId]); } } // Destroy session session_unset(); session_destroy(); $this->logger->info('User logged out successfully', [ 'user_id' => $userId, 'session_id' => $sessionId ]); return ['success' => true, 'message' => 'Logged out successfully']; } catch (Exception $e) { $this->logger->error('Logout error', [ 'error' => $e->getMessage(), 'user_id' => $_SESSION['USER_ID'] ?? 'unknown' ]); return ['success' => false, 'message' => 'An error occurred during logout']; } } /** * Check if user is authenticated * @return bool True if authenticated */ public function isAuthenticated() { if (!isset($_SESSION['USER_ID']) || !isset($_SESSION['SESSION_TOKEN'])) { return false; } // Check session timeout $lastActivity = $_SESSION['LAST_ACTIVITY'] ?? 0; if (time() - $lastActivity > $this->sessionTimeout) { $this->logout(); return false; } // Update last activity $_SESSION['LAST_ACTIVITY'] = time(); // Verify session in database $sessionId = session_id(); $sql = "SELECT `user_id` FROM `db_sessions` WHERE `session_id` = ? AND `expires_at` > NOW()"; $result = $this->db->dbConnection()->Execute($sql, [$sessionId]); if (!$result || $result->EOF) { $this->logout(); return false; } return true; } /** * Get current authenticated user * @return array|null User data or null if not authenticated */ public function getCurrentUser() { if (!$this->isAuthenticated()) { return null; } return [ 'user_id' => $_SESSION['USER_ID'], 'username' => $_SESSION['USERNAME'], 'email' => $_SESSION['EMAIL'], 'role' => $_SESSION['ROLE'] ]; } /** * Request password reset * @param string $email Email address * @return array Result with success status and message */ public function requestPasswordReset($email) { try { $email = VSecurity::validateInput($email, 'email'); if (!$email) { return ['success' => false, 'message' => 'Invalid email address']; } // Rate limiting for password reset requests $rateLimitKey = "password_reset_{$email}"; if (!VSecurity::checkRateLimit($rateLimitKey, 3, 3600, 'password_reset')) { return ['success' => false, 'message' => 'Too many password reset requests. Please try again later.']; } // Find user by email $sql = "SELECT `user_id`, `username`, `email` FROM `db_users` WHERE `email` = ? AND `status` = 'active'"; $result = $this->db->dbConnection()->Execute($sql, [$email]); if (!$result || $result->EOF) { // Don't reveal if email exists or not return ['success' => true, 'message' => 'If the email exists, a password reset link has been sent.']; } $user = $result->fields; // Generate reset token $resetToken = bin2hex(random_bytes(32)); $expiresAt = date('Y-m-d H:i:s', time() + 3600); // 1 hour expiry // Store reset token $tokenSql = "UPDATE `db_users` SET `reset_token` = ?, `reset_expires` = ? WHERE `user_id` = ?"; $tokenResult = $this->db->dbConnection()->Execute($tokenSql, [$resetToken, $expiresAt, $user['user_id']]); if (!$tokenResult) { return ['success' => false, 'message' => 'Failed to generate reset token']; } // Send reset email $this->sendPasswordResetEmail($user['email'], $user['username'], $resetToken); $this->logger->info('Password reset requested', [ 'user_id' => $user['user_id'], 'email' => $email ]); return ['success' => true, 'message' => 'If the email exists, a password reset link has been sent.']; } catch (Exception $e) { $this->logger->error('Password reset request error', [ 'error' => $e->getMessage(), 'email' => $email ?? 'unknown' ]); return ['success' => false, 'message' => 'An error occurred while processing your request']; } } /** * Reset password with token * @param string $token Reset token * @param string $newPassword New password * @return array Result with success status and message */ public function resetPassword($token, $newPassword) { try { $token = VSecurity::validateInput($token, 'alphanum'); if (!$token) { return ['success' => false, 'message' => 'Invalid reset token']; } if (strlen($newPassword) < 8) { return ['success' => false, 'message' => 'Password must be at least 8 characters long']; } if (!$this->isPasswordStrong($newPassword)) { return ['success' => false, 'message' => 'Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character']; } // Find user by reset token $sql = "SELECT `user_id`, `username`, `email` FROM `db_users` WHERE `reset_token` = ? AND `reset_expires` > NOW() AND `status` = 'active'"; $result = $this->db->dbConnection()->Execute($sql, [$token]); if (!$result || $result->EOF) { $this->logger->logSecurityEvent('Invalid password reset attempt', ['token' => $token]); return ['success' => false, 'message' => 'Invalid or expired reset token']; } $user = $result->fields; // Hash new password $passwordHash = password_hash($newPassword, PASSWORD_DEFAULT); // Update password and clear reset token $updateSql = "UPDATE `db_users` SET `password_hash` = ?, `reset_token` = NULL, `reset_expires` = NULL, `updated_at` = NOW() WHERE `user_id` = ?"; $updateResult = $this->db->dbConnection()->Execute($updateSql, [$passwordHash, $user['user_id']]); if (!$updateResult) { return ['success' => false, 'message' => 'Failed to update password']; } // Invalidate all existing sessions for this user $this->invalidateUserSessions($user['user_id']); $this->logger->info('Password reset successfully', [ 'user_id' => $user['user_id'], 'username' => $user['username'] ]); return ['success' => true, 'message' => 'Password reset successfully. Please log in with your new password.']; } catch (Exception $e) { $this->logger->error('Password reset error', [ 'error' => $e->getMessage(), 'token' => $token ?? 'unknown' ]); return ['success' => false, 'message' => 'An error occurred while resetting your password']; } } /** * Helper methods */ private function userExists($username, $email) { $sql = "SELECT COUNT(*) as count FROM `db_users` WHERE `username` = ? OR `email` = ?"; $result = $this->db->dbConnection()->Execute($sql, [$username, $email]); return $result && !$result->EOF && $result->fields['count'] > 0; } private function isPasswordStrong($password) { return preg_match('/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]/', $password); } private function findUserByIdentifier($identifier) { $sql = "SELECT * FROM `db_users` WHERE (`username` = ? OR `email` = ?) AND `status` != 'deleted'"; $result = $this->db->dbConnection()->Execute($sql, [$identifier, $identifier]); return ($result && !$result->EOF) ? $result->fields : null; } private function updateLastLogin($userId) { $sql = "UPDATE `db_users` SET `last_login` = NOW() WHERE `user_id` = ?"; $this->db->dbConnection()->Execute($sql, [$userId]); } private function invalidateUserSessions($userId) { $sql = "DELETE FROM `db_sessions` WHERE `user_id` = ?"; $this->db->dbConnection()->Execute($sql, [$userId]); } private function getClientIP() { $ipKeys = [ 'HTTP_CF_CONNECTING_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR' ]; foreach ($ipKeys as $key) { if (array_key_exists($key, $_SERVER) === true) { $ip = $_SERVER[$key]; if (strpos($ip, ',') !== false) { $ip = explode(',', $ip)[0]; } $ip = trim($ip); if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) { return $ip; } } } return $_SERVER['REMOTE_ADDR'] ?? 'unknown'; } private function sendVerificationEmail($email, $username, $token) { // Implementation depends on your email system // This is a placeholder for the email sending logic $this->logger->info('Verification email sent', [ 'email' => $email, 'username' => $username, 'token' => substr($token, 0, 8) . '...' // Log partial token for debugging ]); } private function sendPasswordResetEmail($email, $username, $token) { // Implementation depends on your email system // This is a placeholder for the email sending logic $this->logger->info('Password reset email sent', [ 'email' => $email, 'username' => $username, 'token' => substr($token, 0, 8) . '...' // Log partial token for debugging ]); } /** * Generate JWT token for API authentication * @param array $user User data * @param int|null $expiryTime Optional custom expiry time in seconds * @return string JWT token */ public function generateJWTToken($user, $expiryTime = null) { try { $expiryTime = $expiryTime ?? (24 * 60 * 60); // Default 24 hours $header = json_encode([ 'typ' => 'JWT', 'alg' => 'HS256' ]); $payload = json_encode([ 'user_id' => $user['user_id'], 'username' => $user['username'], 'email' => $user['email'], 'role' => $user['role'] ?? 'member', 'iat' => time(), 'exp' => time() + $expiryTime ]); $headerEncoded = $this->base64UrlEncode($header); $payloadEncoded = $this->base64UrlEncode($payload); $jwtSecret = getenv('JWT_SECRET') ?: (defined('JWT_SECRET') ? JWT_SECRET : 'change_this_jwt_secret'); $signature = hash_hmac('sha256', $headerEncoded . '.' . $payloadEncoded, $jwtSecret, true); $signatureEncoded = $this->base64UrlEncode($signature); return $headerEncoded . '.' . $payloadEncoded . '.' . $signatureEncoded; } catch (Exception $e) { $this->logger->error('JWT generation failed', [ 'error' => $e->getMessage(), 'user_id' => $user['user_id'] ?? 'unknown' ]); throw $e; } } /** * Validate JWT token * @param string $token JWT token * @return array|null User data or null if invalid */ public function validateJWTToken($token) { try { $parts = explode('.', $token); if (count($parts) !== 3) { $this->logger->logSecurityEvent('Invalid JWT format', ['token' => substr($token, 0, 20) . '...']); return null; } list($headerEncoded, $payloadEncoded, $signatureProvided) = $parts; // Verify signature $jwtSecret = getenv('JWT_SECRET') ?: (defined('JWT_SECRET') ? JWT_SECRET : 'change_this_jwt_secret'); $expectedSignature = hash_hmac('sha256', $headerEncoded . '.' . $payloadEncoded, $jwtSecret, true); $expectedSignatureEncoded = $this->base64UrlEncode($expectedSignature); if (!hash_equals($expectedSignatureEncoded, $signatureProvided)) { $this->logger->logSecurityEvent('JWT signature verification failed', []); return null; } // Decode payload $payload = json_decode($this->base64UrlDecode($payloadEncoded), true); if (!$payload || !isset($payload['user_id'])) { $this->logger->logSecurityEvent('Invalid JWT payload', []); return null; } // Check expiration if (isset($payload['exp']) && $payload['exp'] < time()) { $this->logger->logSecurityEvent('JWT token expired', ['user_id' => $payload['user_id']]); return null; } // Verify user exists and is active $sql = "SELECT `user_id`, `username`, `email`, `role`, `status` FROM `db_users` WHERE `user_id` = ? AND `status` = 'active'"; $result = $this->db->dbConnection()->Execute($sql, [$payload['user_id']]); if (!$result || $result->EOF) { $this->logger->logSecurityEvent('JWT user not found or inactive', ['user_id' => $payload['user_id']]); return null; } $user = $result->fields; return [ 'user_id' => $user['user_id'], 'username' => $user['username'], 'email' => $user['email'], 'role' => $user['role'] ]; } catch (Exception $e) { $this->logger->error('JWT validation error', [ 'error' => $e->getMessage(), 'token' => substr($token, 0, 20) . '...' ]); return null; } } /** * Login with JWT token return (for API clients) * @param string $identifier Username or email * @param string $password Password * @param int|null $expiryTime Optional token expiry time * @return array Result with token and user data */ public function loginWithToken($identifier, $password, $expiryTime = null) { try { // Use regular login to validate credentials $loginResult = $this->login($identifier, $password, false); if (!$loginResult['success']) { return $loginResult; } // Generate JWT token $token = $this->generateJWTToken($loginResult['user'], $expiryTime); return [ 'success' => true, 'message' => 'Login successful', 'token' => $token, 'token_type' => 'Bearer', 'expires_in' => $expiryTime ?? (24 * 60 * 60), 'user' => $loginResult['user'] ]; } catch (Exception $e) { $this->logger->error('Token login error', [ 'error' => $e->getMessage(), 'identifier' => $identifier ?? 'unknown' ]); return ['success' => false, 'message' => 'An error occurred during login']; } } /** * Authenticate request via Bearer token (for API requests) * @param string|null $authHeader Authorization header value * @return array|null User data or null if not authenticated */ public function authenticateBearer($authHeader = null) { try { // Get Authorization header if not provided if ($authHeader === null) { $authHeader = $_SERVER['HTTP_AUTHORIZATION'] ?? $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ?? ''; // Apache mod_rewrite workaround if (empty($authHeader) && function_exists('apache_request_headers')) { $headers = apache_request_headers(); $authHeader = $headers['Authorization'] ?? $headers['authorization'] ?? ''; } } if (empty($authHeader)) { return null; } // Extract Bearer token if (strpos($authHeader, 'Bearer ') === 0) { $token = substr($authHeader, 7); return $this->validateJWTToken($token); } return null; } catch (Exception $e) { $this->logger->error('Bearer authentication error', ['error' => $e->getMessage()]); return null; } } /** * Base64 URL-safe encoding * @param string $data Data to encode * @return string Encoded data */ private function base64UrlEncode($data) { return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); } /** * Base64 URL-safe decoding * @param string $data Data to decode * @return string Decoded data */ private function base64UrlDecode($data) { return base64_decode(strtr($data, '-_', '+/')); } }