Difference between revisions for Users / Eo Ny
|
← Previous edit
|
Next edit →
|
| Version1 | Version2 | Differences |
|---|---|---|
| 1 | 1 | == Session Management Technical Documentation == |
| 2 | {{toc numerate=1}} | |
| 2 | 3 | |
| 3 | 4 | === Overview === |
| 4 | 5 | |
| … | … | … |
| 36 | 37 | |
| 37 | 38 | ==== Session Data Storage ==== |
| 38 | 39 | Session data is stored as an array accessible through ##ArrayObject## interface: |
| 39 |
%% |
|
| 40 | %%(hl php) | |
| 40 | 41 | $session['user_id'] = 123; // Set data |
| 41 | 42 | echo $session['user_id']; // Get data |
| 42 | 43 | %% |
| … | … | … |
| 112 | 113 | All configuration properties are prefixed with ##cf_## (config) and can be set before calling ##start()##: |
| 113 | 114 | |
| 114 | 115 | ===== Session Behavior ===== |
| 115 |
%% |
|
| 116 | %%(hl php) | |
| 116 | 117 | $session->cf_static = 0; // Disable regenerations (e.g., for CAPTCHA) |
| 117 | 118 | $session->cf_max_session = 7200; // Max session lifetime (seconds) |
| 118 | 119 | $session->cf_max_idle = 1440; // Max idle time before destruction (seconds) |
| … | … | … |
| 121 | 122 | %% |
| 122 | 123 | |
| 123 | 124 | ===== Nonce & Replay Protection ===== |
| 124 |
%% |
|
| 125 | %%(hl php) | |
| 125 | 126 | $session->cf_secret = 'adyaiD9+255JeiskPybgisby'; // Secret for nonce generation |
| 126 | 127 | $session->cf_nonce_lifetime = 7200; // Nonce expiration (seconds) |
| 127 | 128 | $session->cf_prevent_replay = 1; // Enable replay attack prevention |
| 128 | 129 | %% |
| 129 | 130 | |
| 130 | 131 | ===== Garbage Collection ===== |
| 131 |
%% |
|
| 132 | %%(hl php) | |
| 132 | 133 | $session->cf_gc_probability = 2; // Probability of GC on shutdown (0-100) |
| 133 | 134 | $session->cf_gc_maxlifetime = 1440; // Max session file lifetime (seconds) |
| 134 | 135 | %% |
| 135 | 136 | |
| 136 | 137 | ===== Cookie Settings ===== |
| 137 |
%% |
|
| 138 | %%(hl php) | |
| 138 | 139 | $session->cf_cookie_prefix = ''; // Prefix for all cookies |
| 139 | 140 | $session->cf_cookie_persistent = false; // Make cookies persistent |
| 140 | 141 | $session->cf_cookie_lifetime = 0; // Cookie lifetime (0 = session cookie) |
| … | … | … |
| 146 | 147 | %% |
| 147 | 148 | |
| 148 | 149 | ===== Cache Control ===== |
| 149 |
%% |
|
| 150 | %%(hl php) | |
| 150 | 151 | $session->cf_cache_limiter = 'none'; // Cache control mode (public|private|nocache|none) |
| 151 | 152 | $session->cf_cache_expire = 180*60; // Cache TTL (seconds) |
| 152 | 153 | $session->cf_cache_mtime = 0; // Modify time for Last-Modified header |
| 153 | 154 | %% |
| 154 | 155 | |
| 155 | 156 | ===== Security Validation ===== |
| 156 |
%% |
|
| 157 | %%(hl php) | |
| 157 | 158 | $session->cf_referer_check = ''; // Check HTTP Referer header |
| 158 | 159 | %% |
| 159 | 160 | |
| 160 | 161 | ===== HTTP Context (Set by HTTP class) ===== |
| 161 |
%% |
|
| 162 | %%(hl php) | |
| 162 | 163 | $session->cf_ip; // Client IP address |
| 163 | 164 | $session->cf_tls; // TLS/SSL connection indicator |
| 164 | 165 | %% |
| … | … | … |
| 169 | 170 | |
| 170 | 171 | ==== Basic Session Setup ==== |
| 171 | 172 | |
| 172 |
%% |
|
| 173 | %%(hl php) | |
| 173 | 174 | // Create a concrete session implementation |
| 174 | 175 | class MySession extends Session { |
| 175 | 176 | // Implement abstract store_* methods |
| … | … | … |
| 202 | 203 | |
| 203 | 204 | ==== Session Data Access ==== |
| 204 | 205 | |
| 205 |
%% |
|
| 206 | %%(hl php) | |
| 206 | 207 | // Array-like access (via ArrayObject) |
| 207 | 208 | $session['user_id'] = 123; |
| 208 | 209 | echo $session['user_id']; |
| … | … | … |
| 215 | 216 | |
| 216 | 217 | ==== Session ID Management ==== |
| 217 | 218 | |
| 218 |
%% |
|
| 219 | %%(hl php) | |
| 219 | 220 | // Get current session ID |
| 220 | 221 | $id = $session->id(); // Returns: e.g., "abc123xyz..." |
| 221 | 222 | |
| … | … | … |
| 228 | 229 | |
| 229 | 230 | ==== Session State ==== |
| 230 | 231 | |
| 231 |
%% |
|
| 232 | %%(hl php) | |
| 232 | 233 | // Check if session is active |
| 233 | 234 | if ($session->active()) { |
| 234 | 235 | // Session is running |
| … | … | … |
| 256 | 257 | - Session validation failures |
| 257 | 258 | |
| 258 | 259 | **Manual Trigger:** |
| 259 |
%% |
|
| 260 | %%(hl php) | |
| 260 | 261 | $session->regenerate_id($delete_old = false, $message = 'custom_reason'); |
| 261 | 262 | %% |
| 262 | 263 | |
| … | … | … |
| 274 | 275 | - Single regeneration per request (checked via ##$this->regenerated## flag) |
| 275 | 276 | - Logged in ##sticky__log## for debugging (max 15 entries) |
| 276 | 277 | |
| 277 |
%% |
|
| 278 | %%(hl php) | |
| 278 | 279 | // Example: Force regeneration on login |
| 279 | 280 | $session->start('myapp'); |
| 280 | 281 | if ($user_authenticated) { |
| … | … | … |
| 294 | 295 | - Useful against bot attacks or stolen sessions |
| 295 | 296 | |
| 296 | 297 | **Configuration:** |
| 297 |
%% |
|
| 298 | %%(hl php) | |
| 298 | 299 | // Automatic on each request (if enabled in code logic) |
| 299 | 300 | // Triggers session destruction if UA changes significantly |
| 300 | 301 | %% |
| … | … | … |
| 310 | 311 | - Tracks IP changes in ##sticky__ip## |
| 311 | 312 | |
| 312 | 313 | **Configuration:** |
| 313 |
%% |
|
| 314 | %%(hl php) | |
| 314 | 315 | $session->cf_ip = $_SERVER['REMOTE_ADDR']; // Set by HTTP class |
| 315 | 316 | // Validation happens automatically during start() |
| 316 | 317 | %% |
| 317 | 318 | |
| 318 | 319 | **IP Change Tracking:** |
| 319 |
%% |
|
| 320 | %%(hl php) | |
| 320 | 321 | // Access IP change history |
| 321 | 322 | $ip_history = $session->sticky__ip; // Array of [ip => change_count] |
| 322 | 323 | %% |
| … | … | … |
| 330 | 331 | - Destroys session on mismatch |
| 331 | 332 | |
| 332 | 333 | **Configuration:** |
| 333 |
%% |
|
| 334 | %%(hl php) | |
| 334 | 335 | $session->cf_tls = !empty($_SERVER['HTTPS']); // Set by HTTP class |
| 335 | 336 | // Validation happens automatically during start() |
| 336 | 337 | %% |
| … | … | … |
| 345 | 346 | - Detects rapid-fire requests (AJAX attacks) |
| 346 | 347 | |
| 347 | 348 | **Configuration:** |
| 348 |
%% |
|
| 349 | %%(hl php) | |
| 349 | 350 | $session->cf_prevent_replay = 1; // Enable (default) |
| 350 | 351 | $session->cf_prevent_replay = 0; // Disable if needed |
| 351 | 352 | %% |
| … | … | … |
| 362 | 363 | **Purpose:** Prevent CSRF via header checking |
| 363 | 364 | |
| 364 | 365 | **Configuration:** |
| 365 |
%% |
|
| 366 | %%(hl php) | |
| 366 | 367 | $session->cf_referer_check = 'example.com'; |
| 367 | 368 | // Session rejected if HTTP_REFERER doesn't contain this string |
| 368 | 369 | %% |
| … | … | … |
| 391 | 392 | - May trigger session ID regeneration |
| 392 | 393 | |
| 393 | 394 | **Example:** |
| 394 |
%% |
|
| 395 | %%(hl php) | |
| 395 | 396 | if ($session->start('webapp', $_COOKIE['sess_id'] ?? null)) { |
| 396 | 397 | // Session ready |
| 397 | 398 | } else { |
| … | … | … |
| 421 | 422 | - Sets ##$active = false## |
| 422 | 423 | |
| 423 | 424 | **Example:** |
| 424 |
%% |
|
| 425 | %%(hl php) | |
| 425 | 426 | $session['key'] = 'value'; |
| 426 | 427 | $session->write_close(); // Ensure data is saved |
| 427 | 428 | %% |
| … | … | … |
| 441 | 442 | - Complete session refresh |
| 442 | 443 | |
| 443 | 444 | **Example:** |
| 444 |
%% |
|
| 445 | %%(hl php) | |
| 445 | 446 | $session->restart(); |
| 446 | 447 | // New session created, old data cleared, sticky_ vars preserved |
| 447 | 448 | %% |
| … | … | … |
| 455 | 456 | |
| 456 | 457 | **Returns:** Session ID string or null if not started |
| 457 | 458 | |
| 458 |
%% |
|
| 459 | %%(hl php) | |
| 459 | 460 | $sid = $session->id(); // "abc123xyz..." |
| 460 | 461 | %% |
| 461 | 462 | |
| … | … | … |
| 466 | 467 | |
| 467 | 468 | **Returns:** Session name |
| 468 | 469 | |
| 469 |
%% |
|
| 470 | %%(hl php) | |
| 470 | 471 | $name = $session->name(); // "myapp" |
| 471 | 472 | %% |
| 472 | 473 | |
| … | … | … |
| 477 | 478 | |
| 478 | 479 | **Returns:** ##true## if session is started and active, ##false## otherwise |
| 479 | 480 | |
| 480 |
%% |
|
| 481 | %%(hl php) | |
| 481 | 482 | if ($session->active()) { |
| 482 | 483 | $session['key'] = 'value'; |
| 483 | 484 | } |
| … | … | … |
| 503 | 504 | - ##null##: No state change |
| 504 | 505 | |
| 505 | 506 | **Example:** |
| 506 |
%% |
|
| 507 | %%(hl php) | |
| 507 | 508 | $session->start('app'); |
| 508 | 509 | if ($message = $session->message()) { |
| 509 | 510 | error_log("Session issue: $message"); |
| … | … | … |
| 519 | 520 | |
| 520 | 521 | **Note:** This is a direct call to ##ArrayObject::getArrayCopy()## |
| 521 | 522 | |
| 522 |
%% |
|
| 523 | %%(hl php) | |
| 523 | 524 | $data = $session->toArray(); |
| 524 | 525 | foreach ($data as $key => $value) { |
| 525 | 526 | echo "$key => $value\n"; |
| … | … | … |
| 540 | 541 | **Returns:** Nonce token string (11 characters) |
| 541 | 542 | |
| 542 | 543 | **Example:** |
| 543 |
%% |
|
| 544 | %%(hl php) | |
| 544 | 545 | $nonce = $session->create_nonce('form_submit', 3600); |
| 545 | 546 | // Use in HTML: <input type="hidden" name="nonce" value="<?= $nonce ?>"> |
| 546 | 547 | %% |
| … | … | … |
| 568 | 569 | - ##-1##: Protected nonce used twice in quick succession (possible AJAX attack) |
| 569 | 570 | |
| 570 | 571 | **Example:** |
| 571 |
%% |
|
| 572 | %%(hl php) | |
| 572 | 573 | if ($nonce = $session->verify_nonce('form_submit', $_POST['nonce'])) { |
| 573 | 574 | if ($nonce === -1) { |
| 574 | 575 | // Possible replay, but might be legitimate AJAX |
| … | … | … |
| 611 | 612 | - Does NOT replace existing cookies (allows multiple Set-Cookie headers) |
| 612 | 613 | |
| 613 | 614 | **Example:** |
| 614 |
%% |
|
| 615 | %%(hl php) | |
| 615 | 616 | // Session cookie |
| 616 | 617 | $session->setcookie('user_pref', 'dark_mode'); |
| 617 | 618 | |
| … | … | … |
| 636 | 637 | |
| 637 | 638 | **Returns:** Cookie value or null if not set |
| 638 | 639 | |
| 639 |
%% |
|
| 640 | %%(hl php) | |
| 640 | 641 | $value = $session->get_cookie('user_pref'); // Reads $_COOKIE['user_pref'] |
| 641 | 642 | %% |
| 642 | 643 | |
| … | … | … |
| 654 | 655 | - ##0##: Use ##cf_cookie_persistent## config |
| 655 | 656 | |
| 656 | 657 | **Example:** |
| 657 |
%% |
|
| 658 | %%(hl php) | |
| 658 | 659 | $session->set_cookie('theme', 'dark'); // Session cookie |
| 659 | 660 | $session->set_cookie('lang', 'en', 365); // 1 year |
| 660 | 661 | %% |
| … | … | … |
| 669 | 670 | |
| 670 | 671 | **Implementation:** Sets empty value with immediate expiration |
| 671 | 672 | |
| 672 |
%% |
|
| 673 | %%(hl php) | |
| 673 | 674 | $session->delete_cookie('old_preference'); |
| 674 | 675 | %% |
| 675 | 676 | |
| … | … | … |
| 678 | 679 | ====== ##unsetcookie($name): void## ====== |
| 679 | 680 | Alias for ##setcookie($name)## with no value (convenience method). |
| 680 | 681 | |
| 681 |
%% |
|
| 682 | %%(hl php) | |
| 682 | 683 | $session->unsetcookie('cookie_name'); |
| 683 | 684 | %% |
| 684 | 685 | |
| … | … | … |
| 699 | 700 | **Default Implementation:** Returns 21-character random alphanumeric string via ##Ut::random_token(21)## |
| 700 | 701 | |
| 701 | 702 | **Override in subclass to customize:** |
| 702 |
%% |
|
| 703 | %%(hl php) | |
| 703 | 704 | protected function store_generate_id(): string { |
| 704 | 705 | return hash('sha256', random_bytes(32)); // Your format |
| 705 | 706 | } |
| … | … | … |
| 713 | 714 | **Default Implementation:** Regex check: ##/^[a-zA-Z\d]{21}$/## |
| 714 | 715 | |
| 715 | 716 | **Override in subclass to match your format:** |
| 716 |
%% |
|
| 717 | %%(hl php) | |
| 717 | 718 | protected function store_validate_id($id): bool { |
| 718 | 719 | return preg_match('/^[a-f0-9]{64}$/', $id); // SHA256 format |
| 719 | 720 | } |
| … | … | … |
| 727 | 728 | **Subclass must implement** - Initialize storage handler |
| 728 | 729 | |
| 729 | 730 | **Example:** |
| 730 |
%% |
|
| 731 | %%(hl php) | |
| 731 | 732 | protected function store_open($name): void { |
| 732 | 733 | $this->db = new PDO('sqlite::memory:'); |
| 733 | 734 | } |
| … | … | … |
| 750 | 751 | - ##false## if session doesn't exist or read error |
| 751 | 752 | |
| 752 | 753 | **Example:** |
| 753 |
%% |
|
| 754 | %%(hl php) | |
| 754 | 755 | protected function store_read($id, $lock = false): string|false { |
| 755 | 756 | $data = file_get_contents("/tmp/sess_$id"); |
| 756 | 757 | return $data ?: false; |
| … | … | … |
| 769 | 770 | - ##$data##: Serialized session data (already processed by ##Ut::serialize()##) |
| 770 | 771 | |
| 771 | 772 | **Example:** |
| 772 |
%% |
|
| 773 | %%(hl php) | |
| 773 | 774 | protected function store_write($id, $data): void { |
| 774 | 775 | file_put_contents("/tmp/sess_$id", $data); |
| 775 | 776 | } |
| … | … | … |
| 783 | 784 | **Subclass must implement** - Release resources |
| 784 | 785 | |
| 785 | 786 | **Example:** |
| 786 |
%% |
|
| 787 | %%(hl php) | |
| 787 | 788 | protected function store_close(): void { |
| 788 | 789 | // Close database, file, etc. |
| 789 | 790 | } |
| … | … | … |
| 803 | 804 | - Sessions older than ##cf_gc_maxlifetime## seconds |
| 804 | 805 | |
| 805 | 806 | **Example:** |
| 806 |
%% |
|
| 807 | %%(hl php) | |
| 807 | 808 | protected function store_gc(): void { |
| 808 | 809 | $max_age = time() - $this->cf_gc_maxlifetime; |
| 809 | 810 | // Delete files/records older than $max_age |
| … | … | … |
| 1038 | 1039 | |
| 1039 | 1040 | ==== Usage ==== |
| 1040 | 1041 | |
| 1041 |
%% |
|
| 1042 | %%(hl php) | |
| 1042 | 1043 | // Store flash message for next request |
| 1043 | 1044 | $session->set_flash('error', 'Username already exists', 1); // 1 request |
| 1044 | 1045 | $session->set_flash('info', 'Welcome back!', 2); // 2 requests |
| … | … | … |
| 1052 | 1053 | - Key: Variable name |
| 1053 | 1054 | - Value: Lifetime in requests |
| 1054 | 1055 | 2. **Cleanup:** In ##terminator()## (shutdown handler): |
| 1055 |
%% |
|
| 1056 | %%(hl php) | |
| 1056 | 1057 | foreach ($sticky__flash as $var => $age) { |
| 1057 | 1058 | if (!isset($session[$var])) { |
| 1058 | 1059 | unset($sticky__flash[$var]); // Already deleted |
| … | … | … |
| 1068 | 1069 | |
| 1069 | 1070 | ==== Example: Login Flow ==== |
| 1070 | 1071 | |
| 1071 |
%% |
|
| 1072 | %%(hl php) | |
| 1072 | 1073 | // POST /login |
| 1073 | 1074 | if ($credentials_valid) { |
| 1074 | 1075 | $session->restart(); // New session |
| … | … | … |
| 1102 | 1103 | |
| 1103 | 1104 | ==== Complete Example: Form Protection ==== |
| 1104 | 1105 | |
| 1105 |
%% |
|
| 1106 | %%(hl php) | |
| 1106 | 1107 | // 1. Display form with nonce |
| 1107 | 1108 | $nonce = $session->create_nonce('user_update', 3600); |
| 1108 | 1109 | ?> |
| … | … | … |
| 1127 | 1128 | |
| 1128 | 1129 | ==== Example: Protected Nonce (AJAX-Safe) ==== |
| 1129 | 1130 | |
| 1130 |
%% |
|
| 1131 | %%(hl php) | |
| 1131 | 1132 | // Generate protected nonce (can verify multiple times) |
| 1132 | 1133 | $nonce = $session->create_nonce('ajax_action', 300); |
| 1133 | 1134 | |
| … | … | … |
| 1181 | 1182 | The ##setcookie()## method implements comprehensive cookie security: |
| 1182 | 1183 | |
| 1183 | 1184 | ===== Encoding ===== |
| 1184 |
%% |
|
| 1185 | %%(hl php) | |
| 1185 | 1186 | // Cookie names: RFC 2616 2.2 token format |
| 1186 | 1187 | // Cookie values: RFC 6265 4.1.1 cookie-octet format |
| 1187 | 1188 | // Unsafe characters automatically URL-encoded |
| 1188 | 1189 | %% |
| 1189 | 1190 | |
| 1190 | 1191 | ===== Security Attributes ===== |
| 1191 |
%% |
|
| 1192 | %%(hl php) | |
| 1192 | 1193 | setcookie('auth', 'token', |
| 1193 | 1194 | expires: time() + 3600, |
| 1194 | 1195 | secure: true, // HTTPS only |
| … | … | … |
| 1198 | 1199 | %% |
| 1199 | 1200 | |
| 1200 | 1201 | ===== No Duplicate Headers ===== |
| 1201 |
%% |
|
| 1202 | %%(hl php) | |
| 1202 | 1203 | // Automatically removes old Set-Cookie header before setting new one |
| 1203 | 1204 | // Prevents cookie header duplication |
| 1204 | 1205 | remove_cookie($name) → clears old headers |
| … | … | … |
| 1207 | 1208 | |
| 1208 | 1209 | ==== Configuration-Driven Defaults ==== |
| 1209 | 1210 | |
| 1210 |
%% |
|
| 1211 | %%(hl php) | |
| 1211 | 1212 | $session->cf_cookie_path = '/app'; // Path |
| 1212 | 1213 | $session->cf_cookie_domain = '.example.com'; // Domain |
| 1213 | 1214 | $session->cf_cookie_secure = true; // HTTPS |
| … | … | … |
| 1221 | 1222 | |
| 1222 | 1223 | ==== Typical Secure Configuration ==== |
| 1223 | 1224 | |
| 1224 |
%% |
|
| 1225 | %%(hl php) | |
| 1225 | 1226 | // Prevent XSS and CSRF |
| 1226 | 1227 | $session->cf_cookie_secure = true; // HTTPS only |
| 1227 | 1228 | $session->cf_cookie_httponly = true; // No JavaScript access |
| … | … | … |
| 1245 | 1246 | The Session class gracefully handles errors: |
| 1246 | 1247 | |
| 1247 | 1248 | ===== Headers Already Sent ===== |
| 1248 |
%% |
|
| 1249 | %%(hl php) | |
| 1249 | 1250 | if (headers_sent($file, $line)) { |
| 1250 | 1251 | trigger_error("id regeneration requested after headers flushed at $file:$line", |
| 1251 | 1252 | E_USER_WARNING); |
| … | … | … |
| 1256 | 1257 | **Impact:** Session ID cannot be regenerated, but session continues |
| 1257 | 1258 | |
| 1258 | 1259 | ===== Cookie Setting Failure ===== |
| 1259 |
%% |
|
| 1260 | %%(hl php) | |
| 1260 | 1261 | if (headers_sent($file, $line)) { |
| 1261 | 1262 | trigger_error("cannot place session cookie $name=$value due to $file:$line", |
| 1262 | 1263 | E_USER_WARNING); |
| … | … | … |
| 1267 | 1268 | **Impact:** Cookie not set, but session data remains accessible |
| 1268 | 1269 | |
| 1269 | 1270 | ===== Storage Errors ===== |
| 1270 |
%% |
|
| 1271 | %%(hl php) | |
| 1271 | 1272 | if ($this->store_read($this->id, true) !== '') { |
| 1272 | 1273 | // error! [comment indicates error, but continues] |
| 1273 | 1274 | } |
| … | … | … |
| 1279 | 1280 | |
| 1280 | 1281 | The Session class includes commented debug statements: |
| 1281 | 1282 | |
| 1282 |
%% |
|
| 1283 | %%(hl php) | |
| 1283 | 1284 | # Ut::dbg("regeneration failed by flush at $file:$line"); |
| 1284 | 1285 | # Ut::dbg($destroy, $message); |
| 1285 | 1286 | # Ut::dbg("session setcookie $name failed by $file:$line"); |
| … | … | … |
| 1291 | 1292 | |
| 1292 | 1293 | Session events tracked in ##sticky__log##: |
| 1293 | 1294 | |
| 1294 |
%% |
|
| 1295 | %%(hl php) | |
| 1295 | 1296 | // Access session event history |
| 1296 | 1297 | if (isset($session->sticky__log)) { |
| 1297 | 1298 | foreach ($session->sticky__log as [$timestamp, $message]) { |
| … | … | … |
| 1314 | 1315 | |
| 1315 | 1316 | ===== File-Based Storage ===== |
| 1316 | 1317 | |
| 1317 |
%% |
|
| 1318 | %%(hl php) | |
| 1318 | 1319 | <?php |
| 1319 | 1320 | |
| 1320 | 1321 | class FileSession extends Session { |
| … | … | … |
| 1375 | 1376 | |
| 1376 | 1377 | ===== Database Storage (PDO) ===== |
| 1377 | 1378 | |
| 1378 |
%% |
|
| 1379 | %%(hl php) | |
| 1379 | 1380 | <?php |
| 1380 | 1381 | |
| 1381 | 1382 | class DatabaseSession extends Session { |
| … | … | … |
| 1443 | 1444 | |
| 1444 | 1445 | ===== Redis Storage ===== |
| 1445 | 1446 | |
| 1446 |
%% |
|
| 1447 | %%(hl php) | |
| 1447 | 1448 | <?php |
| 1448 | 1449 | |
| 1449 | 1450 | class RedisSession extends Session { |
| … | … | … |
| 1493 | 1494 | |
| 1494 | 1495 | ==== Complete Integration Example ==== |
| 1495 | 1496 | |
| 1496 |
%% |
|
| 1497 | %%(hl php) | |
| 1497 | 1498 | <?php |
| 1498 | 1499 | |
| 1499 | 1500 | // Initialize session with configuration |
| … | … | … |
| 1543 | 1544 | |
| 1544 | 1545 | ==== Configuration Best Practices ==== |
| 1545 | 1546 | |
| 1546 |
%% |
|
| 1547 | %%(hl php) | |
| 1547 | 1548 | <?php |
| 1548 | 1549 | |
| 1549 | 1550 | class SessionConfig { |
| … | … | … |
| 1584 | 1585 | |
| 1585 | 1586 | ==== Testing Tips ==== |
| 1586 | 1587 | |
| 1587 |
%% |
|
| 1588 | %%(hl php) | |
| 1588 | 1589 | <?php |
| 1589 | 1590 | |
| 1590 | 1591 | // Test nonce generation and verification |
| … | … | … |
| 1643 | 1644 | |
| 1644 | 1645 | ==== Login Flow ==== |
| 1645 | 1646 | |
| 1646 |
%% |
|
| 1647 | %%(hl php) | |
| 1647 | 1648 | if ($_POST['action'] === 'login') { |
| 1648 | 1649 | $user = authenticate($_POST['username'], $_POST['password']); |
| 1649 | 1650 | if ($user) { |
| … | … | … |
| 1661 | 1662 | |
| 1662 | 1663 | ==== Logout Flow ==== |
| 1663 | 1664 | |
| 1664 |
%% |
|
| 1665 | %%(hl php) | |
| 1665 | 1666 | if ($_GET['action'] === 'logout') { |
| 1666 | 1667 | $session->restart(); // Complete reset |
| 1667 | 1668 | header('Location: /'); |
| … | … | … |
| 1670 | 1671 | |
| 1671 | 1672 | ==== CSRF-Protected Form ==== |
| 1672 | 1673 | |
| 1673 |
%% |
|
| 1674 | %%(hl php) | |
| 1674 | 1675 | // Display form |
| 1675 | 1676 | $csrf = $session->create_nonce('form_' . $form_id, 3600); |
| 1676 | 1677 | echo '<form method="POST">'; |
| … | … | … |
| 1689 | 1690 | |
| 1690 | 1691 | ==== Permission Check with Session Regeneration ==== |
| 1691 | 1692 | |
| 1692 |
%% |
|
| 1693 | %%(hl php) | |
| 1693 | 1694 | if ($user->privilege_level < ADMIN_LEVEL && $promoted_to_admin) { |
| 1694 | 1695 | $session->regenerate_id(false, 'privilege_escalation'); |
| 1695 | 1696 | $session['is_admin'] = true; |
| … | … | … |
| 1698 | 1699 | |
| 1699 | 1700 | ==== Session Messages/Flash ==== |
| 1700 | 1701 | |
| 1701 |
%% |
|
| 1702 | %%(hl php) | |
| 1702 | 1703 | // After action |
| 1703 | 1704 | $session->set_flash('info', 'Profile updated successfully', 1); |
| 1704 | 1705 | |
| … | … | … |
| 1745 | 1746 | |
| 1746 | 1747 | ==== Session Not Starting ==== |
| 1747 | 1748 | |
| 1748 |
%% |
|
| 1749 | %%(hl php) | |
| 1749 | 1750 | if (!$session->start('myapp')) { |
| 1750 | 1751 | // Check reasons: |
| 1751 | 1752 | // 1. Headers already sent? |
| … | … | … |
| 1757 | 1758 | |
| 1758 | 1759 | ==== Cookie Not Setting ==== |
| 1759 | 1760 | |
| 1760 |
%% |
|
| 1761 | %%(hl php) | |
| 1761 | 1762 | // If setcookie() returns false: |
| 1762 | 1763 | // - Check if headers_sent() |
| 1763 | 1764 | // - Check if cookie name is RFC 2616 compliant |
| … | … | … |
| 1766 | 1767 | |
| 1767 | 1768 | ==== Session ID Not Regenerating ==== |
| 1768 | 1769 | |
| 1769 |
%% |
|
| 1770 | %%(hl php) | |
| 1770 | 1771 | // If regenerate_id() returns false: |
| 1771 | 1772 | // - Headers might be sent |
| 1772 | 1773 | // - $active might be false |
| … | … | … |
| 1778 | 1779 | |
| 1779 | 1780 | ==== Nonce Verification Failing ==== |
| 1780 | 1781 | |
| 1781 |
%% |
|
| 1782 | %%(hl php) | |
| 1782 | 1783 | // If verify_nonce() returns false: |
| 1783 | 1784 | // 1. Nonce might be expired |
| 1784 | 1785 | // 2. Nonce might be for different action |
| … | … | … |
| 1791 | 1792 | |
| 1792 | 1793 | ==== Session Data Lost ==== |
| 1793 | 1794 | |
| 1794 |
%% |
|
| 1795 | %%(hl php) | |
| 1795 | 1796 | // Possible causes: |
| 1796 | 1797 | // 1. write_close() not called (usually automatic via shutdown) |
| 1797 | 1798 | // 2. Storage backend failing silently |
| … | … | … |
| 1806 | 1807 | |
| 1807 | 1808 | ---- |
| 1808 | 1809 | |
| 1809 | === TODO Items (From Code Comments) === | |
| 1810 | ||
| 1810 | ===TODO Items (From Code Comments)=== | |
| 1811 | 1811 | The following improvements are planned: |
| 1812 | 1812 | 1. **Do not store session ID in filename or DB index - store hash instead** |
| 1813 |
|
|
| 1814 |
|
|
| 1813 | - Improves security by not exposing IDs in storage layer | |
| 1814 | - Would require hashing logic in store_* methods | |
| 1815 | 1815 | 2. **Log of IP changes and other possible security alerts** |
| 1816 |
|
|
| 1817 |
|
|
| 1816 | - Track ##sticky__ip## changes more comprehensively | |
| 1817 | - Create security audit trail | |
| 1818 | 1818 | 3. **Allocate internal unique session which lives through lifetime of uber-session** |
| 1819 |
|
|
| 1820 |
|
|
| 1819 | - Multi-session management (parent/child sessions) | |
| 1820 | - Useful for complex user flows | |
| 1821 | 1821 | 4. **Do not delete old sessions, but use them as hijack pointers** |
| 1822 |
|
|
| 1823 |
|
|
| 1824 |
|
|
| 1822 | - Maintain session history for analysis | |
| 1823 | - Detect potential session hijacking patterns | |
| 1824 | - Implement session relationship tracking | |
| 1825 | 1825 | 5. **All SIDs used later than ~5secs of regenerations is hijacks** |
| 1826 |
|
|
| 1827 |
|
|
| 1828 |
|
|
| 1826 | - Detect and block delayed session ID usage | |
| 1827 | - Current implementation allows 5-second window | |
| 1828 | - Could be more granular | |
| 1829 | 1829 | |
| 1830 | 1830 | ---- |
| 1831 | 1831 | |
| … | … | … |
| 1852 | 1852 | |
| 1853 | 1853 | ---- |
| 1854 | 1854 | |
| 1855 |
=== |
|
| 1855 | ===Version History=== | |
| 1856 | 1856 | - **Current**: Abstract session class with security features |
| 1857 | 1857 | - **Planned**: Implementation of TODO items above |
| 1858 | 1858 | |
| 1859 | 1859 | ---- |
| 1860 |
|
|
| 1861 |
|
|
| 1860 | **Documentation generated: 2026-05-05** | |
| 1861 | **For latest updates, see: https://github.com/Trojer/wackowiki/blob/main/docs/SESSION_DOCUMENTATION.md** |