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   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  459 %%(hl php)
459 460 $sid = $session->id(); // "abc123xyz..."
460 461 %%
461 462
466 467
467 468 **Returns:** Session name
468 469
469   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  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      %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  1318 %%(hl php)
1318 1319 <?php
1319 1320
1320 1321 class FileSession extends Session {
1375 1376
1376 1377 ===== Database Storage (PDO) =====
1377 1378
1378   %%php
  1379 %%(hl php)
1379 1380 <?php
1380 1381
1381 1382 class DatabaseSession extends Session {
1443 1444
1444 1445 ===== Redis Storage =====
1445 1446
1446   %%php
  1447 %%(hl php)
1447 1448 <?php
1448 1449
1449 1450 class RedisSession extends Session {
1493 1494
1494 1495 ==== Complete Integration Example ====
1495 1496
1496   %%php
  1497 %%(hl php)
1497 1498 <?php
1498 1499
1499 1500 // Initialize session with configuration
1543 1544
1544 1545 ==== Configuration Best Practices ====
1545 1546
1546   %%php
  1547 %%(hl php)
1547 1548 <?php
1548 1549
1549 1550 class SessionConfig {
1584 1585
1585 1586 ==== Testing Tips ====
1586 1587
1587   %%php
  1588 %%(hl php)
1588 1589 <?php
1589 1590
1590 1591 // Test nonce generation and verification
1643 1644
1644 1645 ==== Login Flow ====
1645 1646
1646   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  1702 %%(hl php)
1702 1703 // After action
1703 1704 $session->set_flash('info', 'Profile updated successfully', 1);
1704 1705
1712 1713
1713 1714 === Performance Considerations ===
1714 1715
1715   ==== Optimization Tips ====
  1716 ====Optimization Tips====
1716 1717   1. **Minimize Session Writes:**
1717     - Session data only written during ##write_close()## or regeneration
1718     - No unnecessary serialization during reads
  1718     - Session data only written during ##write_close()## or regeneration
  1719     - No unnecessary serialization during reads
1719 1720   2. **Garbage Collection:**
1720     - Probabilistic GC (based on ##cf_gc_probability##)
1721     - Only runs on ~2% of requests by default
1722     - Customize based on your session volume
  1721     - Probabilistic GC (based on ##cf_gc_probability##)
  1722     - Only runs on ~2% of requests by default
  1723     - Customize based on your session volume
1723 1724   3. **Nonce Cleanup:**
1724     - Expired nonces automatically removed on verification
1725     - Verified nonces removed from storage
1726     - No manual cleanup needed
  1725     - Expired nonces automatically removed on verification
  1726     - Verified nonces removed from storage
  1727     - No manual cleanup needed
1727 1728   4. **Session ID Validation:**
1728     - Regex-based validation is fast
1729     - No database lookup needed
  1729     - Regex-based validation is fast
  1730     - No database lookup needed
1730 1731   5. **Caching Strategy:**
1731     - Cache expensive lookups between session operations
1732     - Session data loaded once per request
  1732     - Cache expensive lookups between session operations
  1733     - Session data loaded once per request
1733 1734
1734 1735 ==== Benchmarks ====
1735 1736
1745 1746
1746 1747 ==== Session Not Starting ====
1747 1748
1748   %%php
  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   %%php
  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   %%php
  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   %%php
  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   %%php
  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     - Improves security by not exposing IDs in storage layer
1814     - Would require hashing logic in store_* methods
  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     - Track ##sticky__ip## changes more comprehensively
1817     - Create security audit trail
  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     - Multi-session management (parent/child sessions)
1820     - Useful for complex user flows
  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     - Maintain session history for analysis
1823     - Detect potential session hijacking patterns
1824     - Implement session relationship tracking
  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     - Detect and block delayed session ID usage
1827     - Current implementation allows 5-second window
1828     - Could be more granular
  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   === Version History ===
  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     *Documentation generated: 2026-05-05*
1861     *For latest updates, see: https://github.com/Trojer/wackowiki/blob/main/docs/SESSION_DOCUMENTATION.md*
  1860 **Documentation generated: 2026-05-05**
  1861 **For latest updates, see: https://github.com/Trojer/wackowiki/blob/main/docs/SESSION_DOCUMENTATION.md**