Difference between revisions for Users / Eo Ny




← Previous edit
Next edit →

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