getHeader('x-user-id'); $fingerprint = $request->getHeader('x-device-fingerprint'); $timestamp = (int)$request->getHeader('x-timestamp'); $nonce = $request->getHeader('x-nonce'); $signature = $request->getHeader('x-signature'); // 1. Check presence of all required security headers if (empty($userId) || empty($fingerprint) || empty($timestamp) || empty($nonce) || empty($signature)) { Response::unauthorized('Security headers missing: X-User-Id, X-Device-Fingerprint, X-Timestamp, X-Nonce, and X-Signature are mandatory.'); } // 2. Anti-replay timestamp check (window of 120 seconds) $currentTime = time(); if (abs($currentTime - $timestamp) > 120) { Response::unauthorized('Request timestamp expired or skewed. Anti-replay check failed.'); } // 3. Query user security record from Database try { $pdo = Database::getConnection(); $stmt = $pdo->prepare(' SELECT us.device_fingerprint, us.device_secret, us.is_locked, u.status FROM user_security us JOIN users u ON u.id = us.user_id WHERE us.user_id = :user_id AND us.device_fingerprint = :fingerprint LIMIT 1 '); $stmt->execute([ ':user_id' => $userId, ':fingerprint' => $fingerprint, ]); $secRecord = $stmt->fetch(PDO::FETCH_ASSOC); if (!$secRecord) { Response::forbidden('Unrecognized device fingerprint or unauthorized device binding.'); } if ((int)$secRecord['is_locked'] === 1 || in_array($secRecord['status'], ['SUSPENDED', 'BANNED'], true)) { Response::forbidden('User device or account has been locked or suspended.'); } // 4. Verify HMAC-SHA256 signature $rawBody = $request->getRawBody(); $bodyHash = hash('sha256', $rawBody); $signPayload = implode("\n", [ $request->getMethod(), $request->getPath(), (string)$timestamp, $nonce, $bodyHash, ]); if (!Security::verifyHmacSignature($signPayload, $signature, $secRecord['device_secret'])) { Response::unauthorized('Invalid cryptographic signature for this device and payload.'); } // 5. Update last active timestamp and IP asynchronously / inline $updateStmt = $pdo->prepare(' UPDATE user_security SET last_active_at = NOW(), last_ip = :ip WHERE user_id = :user_id AND device_fingerprint = :fingerprint '); $updateStmt->execute([ ':ip' => $request->getIp(), ':user_id' => $userId, ':fingerprint' => $fingerprint, ]); } catch (\Throwable $e) { Response::error('Security verification error: ' . $e->getMessage(), 500); } $next($request); } }