Difference between revisions for Users / Eo Ny




← Previous edit
Next edit →

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