How to implement session-based rate limiting instead of IP-based for WordPress plugin? [closed]

1 week ago 12
ARTICLE AD BOX

I have added a small helper here to create/return a stable, server-generated session id stored in a cookie and I changed check_daily_limit to use that session id as the rate-limit key when available (falling back to IP). I kept the rest of your transient logic intact so the change is minimal and safe to drop into your plugin.

Updated code (only the required parts)

Add this helper method to your class (creates a secure random id, sets a cookie with HttpOnly/Secure/SameSite when possible, and returns a session identifier string or empty on failure): private function get_session_identifier() { $cookie_name = 'chat2find_sid'; // If cookie exists and looks valid, return it if (!empty($_COOKIE[$cookie_name])) { $sid = $_COOKIE[$cookie_name]; // validate expected hex format (32 or 64 hex chars) if (preg_match('/^[0-9a-f]{32,64}$/', $sid)) { return 'session_' . $sid; } // invalid cookie: fall through to re-create } // Create a new session id (32 hex chars = 16 bytes) try { $sid = bin2hex(random_bytes(16)); } catch (Exception $e) { // random_bytes failed (very unlikely) -> no session id return ''; } // Cookie lifetime: adjust as needed (example: 30 days) $lifetime = 30 * DAY_IN_SECONDS; $expire = time() + $lifetime; // Set cookie with appropriate flags (PHP 7.3+ supports options array) $secure = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off'); if (PHP_VERSION_ID >= 70300) { setcookie($cookie_name, $sid, [ 'expires' => $expire, 'path' => '/', 'secure' => $secure, 'httponly' => true, 'samesite' => 'Lax', ]); } else { // Best-effort fallback for older PHP: craft header $cookie = rawurlencode($cookie_name) . '=' . rawurlencode($sid) . '; Expires=' . gmdate('D, d M Y H:i:s T', $expire) . '; Path=/' . ($secure ? '; Secure' : '') . '; HttpOnly; SameSite=Lax'; header('Set-Cookie: ' . $cookie, false); } return 'session_' . $sid; } Replace the start of check_daily_limit with using the session identifier (minimal edits; rest of your routine is unchanged). Replace your existing check_daily_limit($ip) with this version: private function check_daily_limit($ip) { $daily_limit = $this->rate_limits['daily']; // Prefer a stable session identifier for anonymous users (avoids shared-IP blocking). // Falls back to IP when session id cannot be created (very rare). $identifier = $this->get_session_identifier(); if (empty($identifier)) { $identifier = 'ip_' . $ip; } $limit_key = 'chat2find_daily_limit_' . md5($identifier); $data = get_transient($limit_key); if ($data === false) { $data = [ 'count' => 1, 'first_request' => time(), 'identifier' => $identifier, 'endpoint' => 'daily' ]; set_transient($limit_key, $data, $daily_limit['seconds']); } else { if ($data['count'] >= $daily_limit['requests']) { $wait_time = $daily_limit['seconds'] - (time() - $data['first_request']); return new WP_Error('daily_rate_limit_exceeded', sprintf('Daily API limit exceeded. Please try again in %d hours.', ceil($wait_time / 3600)), ['status' => 429] ); } $data['count']++; set_transient($limit_key, $data, $daily_limit['seconds']); } return true; }
Read Entire Article