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
4 4 === Overview ===
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
6 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.
7 **Location:** `src/class/session.php` 7
8 **Type:** Abstract class (must be extended with a `SessionStoreInterface` implementation) 8 **Location:** ##src/class/session.php##
9 **Inheritance:** `ArrayObject` 9 **Type:** Abstract class (must be extended with a ##SessionStoreInterface## implementation)
10 10 **Inheritance:** ##ArrayObject##
11 --- 11
12 12 ----
13 ## Table of Contents 13
14 14 === Table of Contents ===
15 1. [Core Concepts](#core-concepts) 15   1. ((#core-concepts Core Concepts))
16 2. [Architecture](#architecture) 16   2. ((#architecture Architecture))
17 3. [Configuration](#configuration) 17   3. ((#configuration Configuration))
18 4. [Usage](#usage) 18   4. ((#usage Usage))
19 5. [Security Features](#security-features) 19   5. ((#security-features Security Features))
20 6. [API Reference](#api-reference) 20   6. ((#api-reference API Reference))
21 7. [Session Lifecycle](#session-lifecycle) 21   7. ((#session-lifecycle Session Lifecycle))
22 8. [Flash Data](#flash-data) 22   8. ((#flash-data Flash Data))
23 9. [Nonce System](#nonce-system) 23   9. ((#nonce-system Nonce System))
24 10. [Cookie Management](#cookie-management) 24   10. ((#cookie-management Cookie Management))
25 11. [Error Handling](#error-handling) 25   11. ((#error-handling Error Handling))
26 12. [Implementation Guide](#implementation-guide) 26   12. ((#implementation-guide Implementation Guide))
27 27
28 --- 28 ----
29 29
30 ## Core Concepts 30 === Core Concepts ===
31 31
32 ### Session State 32 ==== Session State ====
33 The Session class maintains three primary states: 33 The Session class maintains three primary states:
34 34   - **Inactive** (##$active = false##): Session not yet started or has been closed
35 - **Inactive** (`$active = false`): Session not yet started or has been closed 35   - **Active** (##$active = true##): Session is running and can store/retrieve data
36 - **Active** (`$active = true`): Session is running and can store/retrieve data 36   - **Regenerated**: Session ID has been replaced (tracked via ##$regenerated## flag)
37 - **Regenerated**: Session ID has been replaced (tracked via `$regenerated` flag) 37
38 38 ==== Session Data Storage ====
39 ### Session Data Storage 39 Session data is stored as an array accessible through ##ArrayObject## interface:
40 Session data is stored as an array accessible through `ArrayObject` interface: 40 %%(hl php)
41 ```php    
42 $session['user_id'] = 123; // Set data 41 $session['user_id'] = 123; // Set data
43 echo $session['user_id']; // Get data 42 echo $session['user_id']; // Get data
44 ``` 43 %%
45 44
46 ### Sticky Data 45 ==== Sticky Data ====
47 Variables prefixed with `sticky_` are persistent across session resets: 46 Variables prefixed with ##sticky_## are persistent across session resets:
48 - `sticky__created`: Session creation timestamp 47   - ##sticky__created##: Session creation timestamp
49 - `sticky__flash`: Flash data lifetime tracking 48   - ##sticky__flash##: Flash data lifetime tracking
50 - `sticky__log`: Regeneration event log 49   - ##sticky__log##: Regeneration event log
51 - `sticky__ip`: IP change tracking 50   - ##sticky__ip##: IP change tracking
52 51
53 ### Internal Tracking Variables 52 ==== Internal Tracking Variables ====
54 Variables prefixed with `__` are internal session metadata: 53 Variables prefixed with ##__## are internal session metadata:
55 - `__started`: Session start time 54   - ##__started##: Session start time
56 - `__updated`: Last session update time 55   - ##__updated##: Last session update time
57 - `__regenerated`: Last session ID regeneration time 56   - ##__regenerated##: Last session ID regeneration time
58 - `__user_agent`: Client user agent string 57   - ##__user_agent##: Client user agent string
59 - `__user_ip`: Client IP address 58   - ##__user_ip##: Client IP address
60 - `__user_tls`: TLS/SSL status 59   - ##__user_tls##: TLS/SSL status
61 - `__nonces`: Active nonce storage 60   - ##__nonces##: Active nonce storage
62 - `__expire`: Session expiration time (for old sessions) 61   - ##__expire##: Session expiration time (for old sessions)
63 62
64 --- 63 ----
65 64
66 ## Architecture 65 === Architecture ===
67 66
68 ### Class Hierarchy 67 ==== Class Hierarchy ====
69 ``` 68 %%
70 ArrayObject (PHP native) 69 ArrayObject (PHP native)
71     ↓ 70     ↓
72 Session (abstract) 71 Session (abstract)
73     ↓ 72     ↓
74 [Concrete Implementation] (must implement store_* methods) 73 [Concrete Implementation] (must implement store_* methods)
75 ``` 74 %%
76 75
77 ### Key Methods Categories 76 ==== Key Methods Categories ====
78 77
79 **Lifecycle Management:** 78 **Lifecycle Management:**
80 - `__construct()`: Initialize session object 79   - ##__construct()##: Initialize session object
81 - `start()`: Begin a session 80   - ##start()##: Begin a session
82 - `write_close()`: Save and close session 81   - ##write_close()##: Save and close session
83 - `restart()`: Destroy and restart session 82   - ##restart()##: Destroy and restart session
84 - `terminator()`: Shutdown handler (garbage collection, flash data cleanup) 83   - ##terminator()##: Shutdown handler (garbage collection, flash data cleanup)
85 84
86 **Security:** 85 **Security:**
87 - `regenerate_id()`: Replace session ID 86   - ##regenerate_id()##: Replace session ID
88 - `verify_nonce()`: Validate nonce tokens 87   - ##verify_nonce()##: Validate nonce tokens
89 - `prevent_replay()`: Anti-replay protection 88   - ##prevent_replay()##: Anti-replay protection
90 - `create_nonce()`: Generate nonce tokens 89   - ##create_nonce()##: Generate nonce tokens
91 90
92 **Storage (Abstract - Must Implement):** 91 **Storage (Abstract - Must Implement):**
93 - `store_open()`: Open session storage 92   - ##store_open()##: Open session storage
94 - `store_read()`: Read session data 93   - ##store_read()##: Read session data
95 - `store_write()`: Write session data 94   - ##store_write()##: Write session data
96 - `store_close()`: Close session storage 95   - ##store_close()##: Close session storage
97 - `store_gc()`: Garbage collection 96   - ##store_gc()##: Garbage collection
98 - `store_validate_id()`: Validate session ID format 97   - ##store_validate_id()##: Validate session ID format
99 - `store_generate_id()`: Generate new session ID 98   - ##store_generate_id()##: Generate new session ID
100 99
101 **Cookie Management:** 100 **Cookie Management:**
102 - `setcookie()`: Set HTTP cookie with security headers 101   - ##setcookie()##: Set HTTP cookie with security headers
103 - `get_cookie()`: Retrieve cookie value 102   - ##get_cookie()##: Retrieve cookie value
104 - `set_cookie()`: Set cookie (legacy interface) 103   - ##set_cookie()##: Set cookie (legacy interface)
105 - `delete_cookie()`: Remove cookie 104   - ##delete_cookie()##: Remove cookie
106 - `send_cookie()`: Internal cookie transmission 105   - ##send_cookie()##: Internal cookie transmission
107 106
108 --- 107 ----
109 108
110 ## Configuration 109 === Configuration ===
111 110
112 ### Configuration Properties (Public) 111 ==== Configuration Properties (Public) ====
113 112
114 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()##:
115 114
116 #### Session Behavior 115 ===== Session Behavior =====
117 ```php 116 %%(hl php)
118 $session->cf_static = 0; // Disable regenerations (e.g., for CAPTCHA) 117 $session->cf_static = 0; // Disable regenerations (e.g., for CAPTCHA)
119 $session->cf_max_session = 7200; // Max session lifetime (seconds) 118 $session->cf_max_session = 7200; // Max session lifetime (seconds)
120 $session->cf_max_idle = 1440; // Max idle time before destruction (seconds) 119 $session->cf_max_idle = 1440; // Max idle time before destruction (seconds)
121 $session->cf_regen_time = 500; // Seconds between forced ID regenerations 120 $session->cf_regen_time = 500; // Seconds between forced ID regenerations
122 $session->cf_regen_probability = 2; // Percentage probability of forced regen (0-100) 121 $session->cf_regen_probability = 2; // Percentage probability of forced regen (0-100)
123 ``` 122 %%
124 123
125 #### Nonce & Replay Protection 124 ===== Nonce & Replay Protection =====
126 ```php 125 %%(hl php)
127 $session->cf_secret = 'adyaiD9+255JeiskPybgisby'; // Secret for nonce generation 126 $session->cf_secret = 'adyaiD9+255JeiskPybgisby'; // Secret for nonce generation
128 $session->cf_nonce_lifetime = 7200; // Nonce expiration (seconds) 127 $session->cf_nonce_lifetime = 7200; // Nonce expiration (seconds)
129 $session->cf_prevent_replay = 1; // Enable replay attack prevention 128 $session->cf_prevent_replay = 1; // Enable replay attack prevention
130 ``` 129 %%
131 130
132 #### Garbage Collection 131 ===== Garbage Collection =====
133 ```php 132 %%(hl php)
134 $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)
135 $session->cf_gc_maxlifetime = 1440; // Max session file lifetime (seconds) 134 $session->cf_gc_maxlifetime = 1440; // Max session file lifetime (seconds)
136 ``` 135 %%
137 136
138 #### Cookie Settings 137 ===== Cookie Settings =====
139 ```php 138 %%(hl php)
140 $session->cf_cookie_prefix = ''; // Prefix for all cookies 139 $session->cf_cookie_prefix = ''; // Prefix for all cookies
141 $session->cf_cookie_persistent = false; // Make cookies persistent 140 $session->cf_cookie_persistent = false; // Make cookies persistent
142 $session->cf_cookie_lifetime = 0; // Cookie lifetime (0 = session cookie) 141 $session->cf_cookie_lifetime = 0; // Cookie lifetime (0 = session cookie)
145 $session->cf_cookie_secure = false; // HTTPS only 144 $session->cf_cookie_secure = false; // HTTPS only
146 $session->cf_cookie_httponly = true; // Disable JavaScript access 145 $session->cf_cookie_httponly = true; // Disable JavaScript access
147 $session->cf_cookie_samesite = COOKIE_SAMESITE; // SameSite attribute 146 $session->cf_cookie_samesite = COOKIE_SAMESITE; // SameSite attribute
148 ``` 147 %%
149 148
150 #### Cache Control 149 ===== Cache Control =====
151 ```php 150 %%(hl php)
152 $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)
153 $session->cf_cache_expire = 180*60; // Cache TTL (seconds) 152 $session->cf_cache_expire = 180*60; // Cache TTL (seconds)
154 $session->cf_cache_mtime = 0; // Modify time for Last-Modified header 153 $session->cf_cache_mtime = 0; // Modify time for Last-Modified header
155 ``` 154 %%
156 155
157 #### Security Validation 156 ===== Security Validation =====
158 ```php 157 %%(hl php)
159 $session->cf_referer_check = ''; // Check HTTP Referer header 158 $session->cf_referer_check = ''; // Check HTTP Referer header
160 ``` 159 %%
161 160
162 #### HTTP Context (Set by HTTP class) 161 ===== HTTP Context (Set by HTTP class) =====
163 ```php 162 %%(hl php)
164 $session->cf_ip; // Client IP address 163 $session->cf_ip; // Client IP address
165 $session->cf_tls; // TLS/SSL connection indicator 164 $session->cf_tls; // TLS/SSL connection indicator
166 ``` 165 %%
167 166
168 --- 167 ----
169 168
170 ## Usage 169 === Usage ===
171 170
172 ### Basic Session Setup 171 ==== Basic Session Setup ====
173 172
174 ```php 173 %%(hl php)
175 // Create a concrete session implementation 174 // Create a concrete session implementation
176 class MySession extends Session { 175 class MySession extends Session {
177     // Implement abstract store_* methods 176     // Implement abstract store_* methods
200 $session->write_close(); 199 $session->write_close();
201 200
202 // Shutdown handler automatically called via register_shutdown_function() 201 // Shutdown handler automatically called via register_shutdown_function()
203 ``` 202 %%
204 203
205 ### Session Data Access 204 ==== Session Data Access ====
206 205
207 ```php 206 %%(hl php)
208 // Array-like access (via ArrayObject) 207 // Array-like access (via ArrayObject)
209 $session['user_id'] = 123; 208 $session['user_id'] = 123;
210 echo $session['user_id']; 209 echo $session['user_id'];
213 212
214 // Convert to array 213 // Convert to array
215 $all_data = $session->toArray(); 214 $all_data = $session->toArray();
216 ``` 215 %%
217 216
218 ### Session ID Management 217 ==== Session ID Management ====
219 218
220 ```php 219 %%(hl php)
221 // Get current session ID 220 // Get current session ID
222 $id = $session->id(); // Returns: e.g., "abc123xyz..." 221 $id = $session->id(); // Returns: e.g., "abc123xyz..."
223 222
226 225
227 // Get session ID from request 226 // Get session ID from request
228 $session->start('myapp', $_REQUEST['sid'] ?? null); 227 $session->start('myapp', $_REQUEST['sid'] ?? null);
229 ``` 228 %%
230 229
231 ### Session State 230 ==== Session State ====
232 231
233 ```php 232 %%(hl php)
234 // Check if session is active 233 // Check if session is active
235 if ($session->active()) { 234 if ($session->active()) {
236     // Session is running 235     // Session is running
241 240
242 // Restart session (destroy old + start new) 241 // Restart session (destroy old + start new)
243 $session->restart(); 242 $session->restart();
244 ``` 243 %%
245 244
246 --- 245 ----
247 246
248 ## Security Features 247 === Security Features ===
249 248
250 ### 1. Session ID Regeneration 249 ==== 1. Session ID Regeneration ====
251 250
252 **Purpose:** Prevent session fixation attacks 251 **Purpose:** Prevent session fixation attacks
253 252
254 **Automatic Triggers:** 253 **Automatic Triggers:**
255 - Initial session creation (`regenerated = 2`) 254   - Initial session creation (##regenerated = 2##)
256 - First request after creation (`regenerated = 1`) 255   - First request after creation (##regenerated = 1##)
257 - Periodic forced regeneration (based on `cf_regen_time` and `cf_regen_probability`) 256   - Periodic forced regeneration (based on ##cf_regen_time## and ##cf_regen_probability##)
258 - Session validation failures 257   - Session validation failures
259 258
260 **Manual Trigger:** 259 **Manual Trigger:**
261 ```php 260 %%(hl php)
262 $session->regenerate_id($delete_old = false, $message = 'custom_reason'); 261 $session->regenerate_id($delete_old = false, $message = 'custom_reason');
263 ``` 262 %%
264 263
265 **Parameters:** 264 **Parameters:**
266 - `$delete_old`: 265   - ##$delete_old##:
267   - `false` (0): Keep old session active for ~5 seconds (for pending AJAX requests) 266   - ##false## (0): Keep old session active for ~5 seconds (for pending AJAX requests)
268   - `true` (1): Keep old session for time specified (unused in current code) 267   - ##true## (1): Keep old session for time specified (unused in current code)
269   - `2`: Immediately destroy old session 268   - ##2##: Immediately destroy old session
270 269
271 **Implementation Details:** 270 **Implementation Details:**
272 - New session ID is generated via `store_generate_id()` 271   - New session ID is generated via ##store_generate_id()##
273 - Old session data is copied to new ID 272   - Old session data is copied to new ID
274 - Old session marked with `__expire` timestamp 273   - Old session marked with ##__expire## timestamp
275 - Cookie immediately updated with new ID 274   - Cookie immediately updated with new ID
276 - Single regeneration per request (checked via `$this->regenerated` flag) 275   - Single regeneration per request (checked via ##$this->regenerated## flag)
277 - Logged in `sticky__log` for debugging (max 15 entries) 276   - Logged in ##sticky__log## for debugging (max 15 entries)
278 277
279 ```php 278 %%(hl php)
280 // Example: Force regeneration on login 279 // Example: Force regeneration on login
281 $session->start('myapp'); 280 $session->start('myapp');
282 if ($user_authenticated) { 281 if ($user_authenticated) {
283     $session->regenerate_id(false, 'login'); 282     $session->regenerate_id(false, 'login');
284     $session['user_id'] = $user->id; 283     $session['user_id'] = $user->id;
285 } 284 }
286 ``` 285 %%
287 286
288 ### 2. User Agent Validation 287 ==== 2. User Agent Validation ====
289 288
290 **Purpose:** Detect browser/device changes that might indicate hijacking 289 **Purpose:** Detect browser/device changes that might indicate hijacking
291 290
292 **Behavior:** 291 **Behavior:**
293 - Stores user agent on first request 292   - Stores user agent on first request
294 - Compares on subsequent requests using `similar_text()` 293   - Compares on subsequent requests using ##similar_text()##
295 - Destroys session if similarity < 95% 294   - Destroys session if similarity < 95%
296 - Useful against bot attacks or stolen sessions 295   - Useful against bot attacks or stolen sessions
297 296
298 **Configuration:** 297 **Configuration:**
299 ```php 298 %%(hl php)
300 // Automatic on each request (if enabled in code logic) 299 // Automatic on each request (if enabled in code logic)
301 // Triggers session destruction if UA changes significantly 300 // Triggers session destruction if UA changes significantly
302 ``` 301 %%
303 302
304 ### 3. IP Address Validation 303 ==== 3. IP Address Validation ====
305 304
306 **Purpose:** Detect IP spoofing or hijacking 305 **Purpose:** Detect IP spoofing or hijacking
307 306
308 **Behavior:** 307 **Behavior:**
309 - Stores IP on first request 308   - Stores IP on first request
310 - Compares on subsequent requests 309   - Compares on subsequent requests
311 - Soft failure on mismatch: `destroy = 1` (keeps regenerating) 310   - Soft failure on mismatch: ##destroy = 1## (keeps regenerating)
312 - Tracks IP changes in `sticky__ip` 311   - Tracks IP changes in ##sticky__ip##
313 312
314 **Configuration:** 313 **Configuration:**
315 ```php 314 %%(hl php)
316 $session->cf_ip = $_SERVER['REMOTE_ADDR']; // Set by HTTP class 315 $session->cf_ip = $_SERVER['REMOTE_ADDR']; // Set by HTTP class
317 // Validation happens automatically during start() 316 // Validation happens automatically during start()
318 ``` 317 %%
319 318
320 **IP Change Tracking:** 319 **IP Change Tracking:**
321 ```php 320 %%(hl php)
322 // Access IP change history 321 // Access IP change history
323 $ip_history = $session->sticky__ip; // Array of [ip => change_count] 322 $ip_history = $session->sticky__ip; // Array of [ip => change_count]
324 ``` 323 %%
325 324
326 ### 4. TLS/SSL Validation 325 ==== 4. TLS/SSL Validation ====
327 326
328 **Purpose:** Prevent protocol downgrade attacks 327 **Purpose:** Prevent protocol downgrade attacks
329 328
330 **Behavior:** 329 **Behavior:**
331 - Checks if connection transitioned from HTTPS to HTTP 330   - Checks if connection transitioned from HTTPS to HTTP
332 - Destroys session on mismatch 331   - Destroys session on mismatch
333 332
334 **Configuration:** 333 **Configuration:**
335 ```php 334 %%(hl php)
336 $session->cf_tls = !empty($_SERVER['HTTPS']); // Set by HTTP class 335 $session->cf_tls = !empty($_SERVER['HTTPS']); // Set by HTTP class
337 // Validation happens automatically during start() 336 // Validation happens automatically during start()
338 ``` 337 %%
339 338
340 ### 5. Anti-Replay Protection 339 ==== 5. Anti-Replay Protection ====
341 340
342 **Purpose:** Prevent CSRF and replay attacks 341 **Purpose:** Prevent CSRF and replay attacks
343 342
344 **Mechanism:** 343 **Mechanism:**
345 - Generates unique "NoReplay" nonce on each request 344   - Generates unique "NoReplay" nonce on each request
346 - Cookie-based nonce verification 345   - Cookie-based nonce verification
347 - Detects rapid-fire requests (AJAX attacks) 346   - Detects rapid-fire requests (AJAX attacks)
348 347
349 **Configuration:** 348 **Configuration:**
350 ```php 349 %%(hl php)
351 $session->cf_prevent_replay = 1; // Enable (default) 350 $session->cf_prevent_replay = 1; // Enable (default)
352 $session->cf_prevent_replay = 0; // Disable if needed 351 $session->cf_prevent_replay = 0; // Disable if needed
353 ``` 352 %%
354 353
355 **How It Works:** 354 **How It Works:**
356 ``` 355 %%
357 Request 1: Generate nonce, send in cookie 356 Request 1: Generate nonce, send in cookie
358 Request 2: Client sends nonce back, verify & generate new one 357 Request 2: Client sends nonce back, verify & generate new one
359 Request 3: If old nonce used again → reject (replay detected) 358 Request 3: If old nonce used again → reject (replay detected)
360 ``` 359 %%
361 360
362 ### 6. Referer Validation (Optional) 361 ==== 6. Referer Validation (Optional) ====
363 362
364 **Purpose:** Prevent CSRF via header checking 363 **Purpose:** Prevent CSRF via header checking
365 364
366 **Configuration:** 365 **Configuration:**
367 ```php 366 %%(hl php)
368 $session->cf_referer_check = 'example.com'; 367 $session->cf_referer_check = 'example.com';
369 // Session rejected if HTTP_REFERER doesn't contain this string 368 // Session rejected if HTTP_REFERER doesn't contain this string
370 ``` 369 %%
371 370
372 --- 371 ----
373 372
374 ## API Reference 373 === API Reference ===
375 374
376 ### Public Methods 375 ==== Public Methods ====
377 376
378 #### Lifecycle Management 377 ===== Lifecycle Management =====
379 378
380 ##### `start($name = null, $id = null): bool` 379 ====== ##start($name = null, $id = null): bool## ======
381 Start or resume a session. 380 Start or resume a session.
382 381
383 **Parameters:** 382 **Parameters:**
384 - `$name` (string|null): Session name (cookie name base). Alphanumeric + underscore/dash. Defaults to 'sesid' 383   - ##$name## (string|null): Session name (cookie name base). Alphanumeric + underscore/dash. Defaults to 'sesid'
385 - `$id` (string|null): Existing session ID to resume. If null, attempts to read from cookie 384   - ##$id## (string|null): Existing session ID to resume. If null, attempts to read from cookie
386 385
387 **Returns:** `true` if session started successfully, `false` on error 386 **Returns:** ##true## if session started successfully, ##false## on error
388 387
389 **Side Effects:** 388 **Side Effects:**
390 - Sets headers (cookies, cache control) 389   - Sets headers (cookies, cache control)
391 - Populates session data from storage 390   - Populates session data from storage
392 - Performs security validations 391   - Performs security validations
393 - May trigger session ID regeneration 392   - May trigger session ID regeneration
394 393
395 **Example:** 394 **Example:**
396 ```php 395 %%(hl php)
397 if ($session->start('webapp', $_COOKIE['sess_id'] ?? null)) { 396 if ($session->start('webapp', $_COOKIE['sess_id'] ?? null)) {
398     // Session ready 397     // Session ready
399 } else { 398 } else {
400     // Session failed 399     // Session failed
401 } 400 }
402 ``` 401 %%
403 402
404 **Validation Steps:** 403 **Validation Steps:**
405 1. Reject if headers already sent 404   1. Reject if headers already sent
406 2. Validate session name format 405   2. Validate session name format
407 3. Retrieve ID from parameter or cookie 406   3. Retrieve ID from parameter or cookie
408 4. Check Referer header (if configured) 407   4. Check Referer header (if configured)
409 5. Validate ID format via `store_validate_id()` 408   5. Validate ID format via ##store_validate_id()##
410 6. Read session data from storage 409   6. Read session data from storage
411 7. Verify nonces and timestamps 410   7. Verify nonces and timestamps
412 8. Check user agent, IP, TLS 411   8. Check user agent, IP, TLS
413 9. Regenerate if needed 412   9. Regenerate if needed
414 413
415 --- 414 ----
416 415
417 ##### `write_close(): void` 416 ====== ##write_close(): void## ======
418 Save session data and close session. 417 Save session data and close session.
419 418
420 **Side Effects:** 419 **Side Effects:**
421 - Calls `write_session()` to serialize and store data 420   - Calls ##write_session()## to serialize and store data
422 - Calls `store_close()` to close storage handler 421   - Calls ##store_close()## to close storage handler
423 - Sets `$active = false` 422   - Sets ##$active = false##
424 423
425 **Example:** 424 **Example:**
426 ```php 425 %%(hl php)
427 $session['key'] = 'value'; 426 $session['key'] = 'value';
428 $session->write_close(); // Ensure data is saved 427 $session->write_close(); // Ensure data is saved
429 ``` 428 %%
430 429
431 --- 430 ----
432 431
433 ##### `restart(): bool` 432 ====== ##restart(): bool## ======
434 Destroy current session and create new one. 433 Destroy current session and create new one.
435 434
436 **Equivalent to:** `regenerate_id(true) + clean_vars() + populate()` 435 **Equivalent to:** ##regenerate_id(true) + clean_vars() + populate()##
437 436
438 **Returns:** `true` on success, `false` on error 437 **Returns:** ##true## on success, ##false## on error
439 438
440 **Use Cases:** 439 **Use Cases:**
441 - User logout and new login 440   - User logout and new login
442 - Security reset 441   - Security reset
443 - Complete session refresh 442   - Complete session refresh
444 443
445 **Example:** 444 **Example:**
446 ```php 445 %%(hl php)
447 $session->restart(); 446 $session->restart();
448 // New session created, old data cleared, sticky_ vars preserved 447 // New session created, old data cleared, sticky_ vars preserved
449 ``` 448 %%
450 449
451 --- 450 ----
452 451
453 #### Session Access 452 ===== Session Access =====
454 453
455 ##### `id(): mixed` 454 ====== ##id(): mixed## ======
456 Get current session ID. 455 Get current session ID.
457 456
458 **Returns:** Session ID string or null if not started 457 **Returns:** Session ID string or null if not started
459 458
460 ```php 459 %%(hl php)
461 $sid = $session->id(); // "abc123xyz..." 460 $sid = $session->id(); // "abc123xyz..."
462 ``` 461 %%
463 462
464 --- 463 ----
465 464
466 ##### `name(): string` 465 ====== ##name(): string## ======
467 Get session name (cookie prefix). 466 Get session name (cookie prefix).
468 467
469 **Returns:** Session name 468 **Returns:** Session name
470 469
471 ```php 470 %%(hl php)
472 $name = $session->name(); // "myapp" 471 $name = $session->name(); // "myapp"
473 ``` 472 %%
474 473
475 --- 474 ----
476 475
477 ##### `active(): bool` 476 ====== ##active(): bool## ======
478 Check if session is currently active. 477 Check if session is currently active.
479 478
480 **Returns:** `true` if session is started and active, `false` otherwise 479 **Returns:** ##true## if session is started and active, ##false## otherwise
481 480
482 ```php 481 %%(hl php)
483 if ($session->active()) { 482 if ($session->active()) {
484     $session['key'] = 'value'; 483     $session['key'] = 'value';
485 } 484 }
486 ``` 485 %%
487 486
488 --- 487 ----
489 488
490 ##### `message(): string|null` 489 ====== ##message(): string|null## ======
491 Get reason for last session state change. 490 Get reason for last session state change.
492 491
493 **Returns:** Message string or null 492 **Returns:** Message string or null
494 493
495 **Possible Values:** 494 **Possible Values:**
496 - `'replay'`: Replay attack detected 495   - ##'replay'##: Replay attack detected
497 - `'obsolete'`: Session marked for expiration 496   - ##'obsolete'##: Session marked for expiration
498 - `'reg_expire'`: Regeneration expiration reached 497   - ##'reg_expire'##: Regeneration expiration reached
499 - `'max_session'`: Max session lifetime exceeded 498   - ##'max_session'##: Max session lifetime exceeded
500 - `'max_idle'`: Idle timeout exceeded 499   - ##'max_idle'##: Idle timeout exceeded
501 - `'ua'`: User agent mismatch (>5% difference) 500   - ##'ua'##: User agent mismatch (>5% difference)
502 - `'tls'`: TLS status changed 501   - ##'tls'##: TLS status changed
503 - `'ip'`: IP address mismatch 502   - ##'ip'##: IP address mismatch
504 - `'restart'`: Session manually restarted 503   - ##'restart'##: Session manually restarted
505 - `null`: No state change 504   - ##null##: No state change
506 505
507 **Example:** 506 **Example:**
508 ```php 507 %%(hl php)
509 $session->start('app'); 508 $session->start('app');
510 if ($message = $session->message()) { 509 if ($message = $session->message()) {
511     error_log("Session issue: $message"); 510     error_log("Session issue: $message");
512 } 511 }
513 ``` 512 %%
514 513
515 --- 514 ----
516 515
517 ##### `toArray(): array` 516 ====== ##toArray(): array## ======
518 Convert session data to array. 517 Convert session data to array.
519 518
520 **Returns:** Associative array of session data 519 **Returns:** Associative array of session data
521 520
522 **Note:** This is a direct call to `ArrayObject::getArrayCopy()` 521 **Note:** This is a direct call to ##ArrayObject::getArrayCopy()##
523 522
524 ```php 523 %%(hl php)
525 $data = $session->toArray(); 524 $data = $session->toArray();
526 foreach ($data as $key => $value) { 525 foreach ($data as $key => $value) {
527     echo "$key => $value\n"; 526     echo "$key => $value\n";
528 } 527 }
529 ``` 528 %%
530 529
531 --- 530 ----
532 531
533 #### Nonce System 532 ===== Nonce System =====
534 533
535 ##### `create_nonce($action, $expires = null): string` 534 ====== ##create_nonce($action, $expires = null): string## ======
536 Generate a unique nonce token. 535 Generate a unique nonce token.
537 536
538 **Parameters:** 537 **Parameters:**
539 - `$action` (string): Action identifier (e.g., 'form_submit', 'delete_action') 538   - ##$action## (string): Action identifier (e.g., 'form_submit', 'delete_action')
540 - `$expires` (int|null): Expiration time in seconds. Defaults to `cf_nonce_lifetime` 539   - ##$expires## (int|null): Expiration time in seconds. Defaults to ##cf_nonce_lifetime##
541 540
542 **Returns:** Nonce token string (11 characters) 541 **Returns:** Nonce token string (11 characters)
543 542
544 **Example:** 543 **Example:**
545 ```php 544 %%(hl php)
546 $nonce = $session->create_nonce('form_submit', 3600); 545 $nonce = $session->create_nonce('form_submit', 3600);
547 // Use in HTML: <input type="hidden" name="nonce" value="<?= $nonce ?>"> 546 // Use in HTML: <input type="hidden" name="nonce" value="<?= $nonce ?>">
548 ``` 547 %%
549 548
550 **Storage:** 549 **Storage:**
551 - Stored in `$session->__nonces[]` 550   - Stored in ##$session->__nonces[]##
552 - Key: `{action}.{base64_encoded_hash}` 551   - Key: ##{action}.{base64_encoded_hash}##
553 - Value: Expiration timestamp 552   - Value: Expiration timestamp
554 553
555 --- 554 ----
556 555
557 ##### `verify_nonce($action, $code, $protect = 0)` 556 ====== ##verify_nonce($action, $code, $protect = 0)## ======
558 Verify a nonce token. 557 Verify a nonce token.
559 558
560 **Parameters:** 559 **Parameters:**
561 - `$action` (string): Action identifier that was used in `create_nonce()` 560   - ##$action## (string): Action identifier that was used in ##create_nonce()##
562 - `$code` (string): Nonce token from user 561   - ##$code## (string): Nonce token from user
563 - `$protect` (int): Protection level 562   - ##$protect## (int): Protection level
564   - `0`: Single-use nonce (consumed on first verification) 563   - ##0##: Single-use nonce (consumed on first verification)
565   - `1+`: Protected nonce (can verify multiple times, prevents fast replays) 564   - ##1+##: Protected nonce (can verify multiple times, prevents fast replays)
566 565
567 **Returns:** 566 **Returns:**
568 - `true` (1): Nonce verified and valid 567   - ##true## (1): Nonce verified and valid
569 - `false` (0): Nonce invalid or expired 568   - ##false## (0): Nonce invalid or expired
570 - `-1`: Protected nonce used twice in quick succession (possible AJAX attack) 569   - ##-1##: Protected nonce used twice in quick succession (possible AJAX attack)
571 570
572 **Example:** 571 **Example:**
573 ```php 572 %%(hl php)
574 if ($nonce = $session->verify_nonce('form_submit', $_POST['nonce'])) { 573 if ($nonce = $session->verify_nonce('form_submit', $_POST['nonce'])) {
575     if ($nonce === -1) { 574     if ($nonce === -1) {
576         // Possible replay, but might be legitimate AJAX 575         // Possible replay, but might be legitimate AJAX
580         process_form(); 579         process_form();
581     } 580     }
582 } 581 }
583 ``` 582 %%
584 583
585 **Cleanup:** 584 **Cleanup:**
586 - Expired nonces automatically removed 585   - Expired nonces automatically removed
587 - Verified single-use nonces removed from storage 586   - Verified single-use nonces removed from storage
588 587
589 --- 588 ----
590 589
591 #### Cookie Management 590 ===== Cookie Management =====
592 591
593 ##### `setcookie($name, $value = null, $expires = 0, $path = null, $domain = null, $secure = null, $httponly = null, $samesite = null): bool` 592 ====== ##setcookie($name, $value = null, $expires = 0, $path = null, $domain = null, $secure = null, $httponly = null, $samesite = null): bool## ======
594 Set a cookie with security headers. 593 Set a cookie with security headers.
595 594
596 **Parameters:** 595 **Parameters:**
597 - `$name`: Cookie name (automatically URL-encoded) 596   - ##$name##: Cookie name (automatically URL-encoded)
598 - `$value`: Cookie value (automatically URL-encoded, null to delete) 597   - ##$value##: Cookie value (automatically URL-encoded, null to delete)
599 - `$expires`: Expiration timestamp (0 = session cookie) 598   - ##$expires##: Expiration timestamp (0 = session cookie)
600 - `$path`: Cookie path (default: `cf_cookie_path`) 599   - ##$path##: Cookie path (default: ##cf_cookie_path##)
601 - `$domain`: Cookie domain (default: `cf_cookie_domain`) 600   - ##$domain##: Cookie domain (default: ##cf_cookie_domain##)
602 - `$secure`: HTTPS only (default: `cf_cookie_secure`) 601   - ##$secure##: HTTPS only (default: ##cf_cookie_secure##)
603 - `$httponly`: Disable JS access (default: `cf_cookie_httponly`) 602   - ##$httponly##: Disable JS access (default: ##cf_cookie_httponly##)
604 - `$samesite`: SameSite attribute (default: `cf_cookie_samesite`) 603   - ##$samesite##: SameSite attribute (default: ##cf_cookie_samesite##)
605 604
606 **Returns:** `true` on success, `false` if headers already sent 605 **Returns:** ##true## on success, ##false## if headers already sent
607 606
608 **Features:** 607 **Features:**
609 - RFC 2616 2.2 token encoding for cookie name 608   - RFC 2616 2.2 token encoding for cookie name
610 - RFC 6265 4.1.1 cookie-octet encoding for value 609   - RFC 6265 4.1.1 cookie-octet encoding for value
611 - Removes duplicate cookie headers automatically 610   - Removes duplicate cookie headers automatically
612 - Adds all security attributes (secure, httponly, samesite) 611   - Adds all security attributes (secure, httponly, samesite)
613 - Does NOT replace existing cookies (allows multiple Set-Cookie headers) 612   - Does NOT replace existing cookies (allows multiple Set-Cookie headers)
614 613
615 **Example:** 614 **Example:**
616 ```php 615 %%(hl php)
617 // Session cookie 616 // Session cookie
618 $session->setcookie('user_pref', 'dark_mode'); 617 $session->setcookie('user_pref', 'dark_mode');
619 618
626 // Secure cookie with SameSite 625 // Secure cookie with SameSite
627 $session->setcookie('token', 'abc123', time() + 3600, 626 $session->setcookie('token', 'abc123', time() + 3600,
628     path: '/', secure: true, httponly: true, samesite: 'Strict'); 627     path: '/', secure: true, httponly: true, samesite: 'Strict');
629 ``` 628 %%
630 629
631 --- 630 ----
632 631
633 ##### `get_cookie($name)` 632 ====== ##get_cookie($name)## ======
634 Retrieve cookie value. 633 Retrieve cookie value.
635 634
636 **Parameters:** 635 **Parameters:**
637 - `$name`: Cookie name (prefix automatically added) 636   - ##$name##: Cookie name (prefix automatically added)
638 637
639 **Returns:** Cookie value or null if not set 638 **Returns:** Cookie value or null if not set
640 639
641 ```php 640 %%(hl php)
642 $value = $session->get_cookie('user_pref'); // Reads $_COOKIE['user_pref'] 641 $value = $session->get_cookie('user_pref'); // Reads $_COOKIE['user_pref']
643 ``` 642 %%
644 643
645 --- 644 ----
646 645
647 ##### `set_cookie($name, $value, $persistent = false): void` 646 ====== ##set_cookie($name, $value, $persistent = false): void## ======
648 Legacy cookie setter (alternative to `setcookie()`). 647 Legacy cookie setter (alternative to ##setcookie()##).
649 648
650 **Parameters:** 649 **Parameters:**
651 - `$name`: Cookie name (prefix added) 650   - ##$name##: Cookie name (prefix added)
652 - `$value`: Cookie value 651   - ##$value##: Cookie value
653 - `$persistent`: 652   - ##$persistent##:
654   - `false`: Session cookie (deleted on browser close) 653   - ##false##: Session cookie (deleted on browser close)
655   - Number: Days to persist 654   - Number: Days to persist
656   - `0`: Use `cf_cookie_persistent` config 655   - ##0##: Use ##cf_cookie_persistent## config
657 656
658 **Example:** 657 **Example:**
659 ```php 658 %%(hl php)
660 $session->set_cookie('theme', 'dark'); // Session cookie 659 $session->set_cookie('theme', 'dark'); // Session cookie
661 $session->set_cookie('lang', 'en', 365); // 1 year 660 $session->set_cookie('lang', 'en', 365); // 1 year
662 ``` 661 %%
663 662
664 --- 663 ----
665 664
666 ##### `delete_cookie($name): void` 665 ====== ##delete_cookie($name): void## ======
667 Delete a cookie. 666 Delete a cookie.
668 667
669 **Parameters:** 668 **Parameters:**
670 - `$name`: Cookie name (prefix added) 669   - ##$name##: Cookie name (prefix added)
671 670
672 **Implementation:** Sets empty value with immediate expiration 671 **Implementation:** Sets empty value with immediate expiration
673 672
674 ```php 673 %%(hl php)
675 $session->delete_cookie('old_preference'); 674 $session->delete_cookie('old_preference');
676 ``` 675 %%
677 676
678 --- 677 ----
679 678
680 ##### `unsetcookie($name): void` 679 ====== ##unsetcookie($name): void## ======
681 Alias for `setcookie($name)` with no value (convenience method). 680 Alias for ##setcookie($name)## with no value (convenience method).
682 681
683 ```php 682 %%(hl php)
684 $session->unsetcookie('cookie_name'); 683 $session->unsetcookie('cookie_name');
685 ``` 684 %%
686 685
687 --- 686 ----
688 687
689 ### Protected Methods (For Store Implementation) 688 ==== Protected Methods (For Store Implementation) ====
690 689
691 #### `regenerate_id($delete_old = false, $message = ''): bool` 690 ===== ##regenerate_id($delete_old = false, $message = ''): bool## =====
692 Internal method to regenerate session ID (called automatically). 691 Internal method to regenerate session ID (called automatically).
693 692
694 **Protected** - Usually called automatically, but can be overridden/called by subclasses 693 **Protected** - Usually called automatically, but can be overridden/called by subclasses
695 694
696 --- 695 ----
697 696
698 #### `store_generate_id(): string` 697 ===== ##store_generate_id(): string## =====
699 Generate a new session ID. 698 Generate a new session ID.
700 699
701 **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)##
702 701
703 **Override in subclass to customize:** 702 **Override in subclass to customize:**
704 ```php 703 %%(hl php)
705 protected function store_generate_id(): string { 704 protected function store_generate_id(): string {
706     return hash('sha256', random_bytes(32)); // Your format 705     return hash('sha256', random_bytes(32)); // Your format
707 } 706 }
708 ``` 707 %%
709 708
710 --- 709 ----
711 710
712 #### `store_validate_id($id): bool` 711 ===== ##store_validate_id($id): bool## =====
713 Validate session ID format. 712 Validate session ID format.
714 713
715 **Default Implementation:** Regex check: `/^[a-zA-Z\d]{21}$/` 714 **Default Implementation:** Regex check: ##/^[a-zA-Z\d]{21}$/##
716 715
717 **Override in subclass to match your format:** 716 **Override in subclass to match your format:**
718 ```php 717 %%(hl php)
719 protected function store_validate_id($id): bool { 718 protected function store_validate_id($id): bool {
720     return preg_match('/^[a-f0-9]{64}$/', $id); // SHA256 format 719     return preg_match('/^[a-f0-9]{64}$/', $id); // SHA256 format
721 } 720 }
722 ``` 721 %%
723 722
724 --- 723 ----
725 724
726 #### `store_open($name): void` 725 ===== ##store_open($name): void## =====
727 Open session storage (called before first read/write). 726 Open session storage (called before first read/write).
728 727
729 **Subclass must implement** - Initialize storage handler 728 **Subclass must implement** - Initialize storage handler
730 729
731 **Example:** 730 **Example:**
732 ```php 731 %%(hl php)
733 protected function store_open($name): void { 732 protected function store_open($name): void {
734     $this->db = new PDO('sqlite::memory:'); 733     $this->db = new PDO('sqlite::memory:');
735 } 734 }
736 ``` 735 %%
737 736
738 --- 737 ----
739 738
740 #### `store_read($id, $lock = false): string|false` 739 ===== ##store_read($id, $lock = false): string|false## =====
741 Read session data from storage. 740 Read session data from storage.
742 741
743 **Subclass must implement** 742 **Subclass must implement**
744 743
745 **Parameters:** 744 **Parameters:**
746 - `$id`: Session ID to read 745   - ##$id##: Session ID to read
747 - `$lock`: If true, lock the session file for writing (create new) 746   - ##$lock##: If true, lock the session file for writing (create new)
748 747
749 **Returns:** 748 **Returns:**
750 - Serialized session data (string) if found and locked 749   - Serialized session data (string) if found and locked
751 - Empty string (`''`) if new session should be created 750   - Empty string (##''##) if new session should be created
752 - `false` if session doesn't exist or read error 751   - ##false## if session doesn't exist or read error
753 752
754 **Example:** 753 **Example:**
755 ```php 754 %%(hl php)
756 protected function store_read($id, $lock = false): string|false { 755 protected function store_read($id, $lock = false): string|false {
757     $data = file_get_contents("/tmp/sess_$id"); 756     $data = file_get_contents("/tmp/sess_$id");
758     return $data ?: false; 757     return $data ?: false;
759 } 758 }
760 ``` 759 %%
761 760
762 --- 761 ----
763 762
764 #### `store_write($id, $data): void` 763 ===== ##store_write($id, $data): void## =====
765 Write session data to storage. 764 Write session data to storage.
766 765
767 **Subclass must implement** 766 **Subclass must implement**
768 767
769 **Parameters:** 768 **Parameters:**
770 - `$id`: Session ID 769   - ##$id##: Session ID
771 - `$data`: Serialized session data (already processed by `Ut::serialize()`) 770   - ##$data##: Serialized session data (already processed by ##Ut::serialize()##)
772 771
773 **Example:** 772 **Example:**
774 ```php 773 %%(hl php)
775 protected function store_write($id, $data): void { 774 protected function store_write($id, $data): void {
776     file_put_contents("/tmp/sess_$id", $data); 775     file_put_contents("/tmp/sess_$id", $data);
777 } 776 }
778 ``` 777 %%
779 778
780 --- 779 ----
781 780
782 #### `store_close(): void` 781 ===== ##store_close(): void## =====
783 Close session storage. 782 Close session storage.
784 783
785 **Subclass must implement** - Release resources 784 **Subclass must implement** - Release resources
786 785
787 **Example:** 786 **Example:**
788 ```php 787 %%(hl php)
789 protected function store_close(): void { 788 protected function store_close(): void {
790     // Close database, file, etc. 789     // Close database, file, etc.
791 } 790 }
792 ``` 791 %%
793 792
794 --- 793 ----
795 794
796 #### `store_gc(): void` 795 ===== ##store_gc(): void## =====
797 Perform garbage collection on old sessions. 796 Perform garbage collection on old sessions.
798 797
799 **Subclass must implement** - Delete expired sessions 798 **Subclass must implement** - Delete expired sessions
800 799
801 **Called During:** 800 **Called During:**
802 - Shutdown handler (probabilistic, based on `cf_gc_probability`) 801   - Shutdown handler (probabilistic, based on ##cf_gc_probability##)
803 802
804 **Should Delete:** 803 **Should Delete:**
805 - Sessions older than `cf_gc_maxlifetime` seconds 804   - Sessions older than ##cf_gc_maxlifetime## seconds
806 805
807 **Example:** 806 **Example:**
808 ```php 807 %%(hl php)
809 protected function store_gc(): void { 808 protected function store_gc(): void {
810     $max_age = time() - $this->cf_gc_maxlifetime; 809     $max_age = time() - $this->cf_gc_maxlifetime;
811     // Delete files/records older than $max_age 810     // Delete files/records older than $max_age
812 } 811 }
813 ``` 812 %%
814 813
815 --- 814 ----
816 815
817 ### Private Methods (Internal Use) 816 ==== Private Methods (Internal Use) ====
818 817
819 ##### `populate(): void` 818 ====== ##populate(): void## ======
820 Initialize session tracking variables on first request. 819 Initialize session tracking variables on first request.
821 820
822 **Called by:** `start()`, `restart()` 821 **Called by:** ##start()##, ##restart()##
823 822
824 **Initializes:** 823 **Initializes:**
825 - `__started`: Current timestamp 824   - ##__started##: Current timestamp
826 - `__regenerated`: Current timestamp 825   - ##__regenerated##: Current timestamp
827 - `__user_agent`: Browser user agent 826   - ##__user_agent##: Browser user agent
828 - `__user_ip`: Client IP (if configured) 827   - ##__user_ip##: Client IP (if configured)
829 - `__user_tls`: TLS status (if configured) 828   - ##__user_tls##: TLS status (if configured)
830 - `sticky__created`: Creation time (if not exists) 829   - ##sticky__created##: Creation time (if not exists)
831 830
832 --- 831 ----
833 832
834 ##### `write_session(): void` 833 ====== ##write_session(): void## ======
835 Serialize and write session data to storage. 834 Serialize and write session data to storage.
836 835
837 **Called by:** `regenerate_id()`, `write_close()`, `terminator()` 836 **Called by:** ##regenerate_id()##, ##write_close()##, ##terminator()##
838 837
839 **Updates:** 838 **Updates:**
840 - `__updated`: Current timestamp 839   - ##__updated##: Current timestamp
841 - Calls `store_write()` with serialized data 840   - Calls ##store_write()## with serialized data
842 841
843 --- 842 ----
844 843
845 ##### `clean_vars(): void` 844 ====== ##clean_vars(): void## ======
846 Remove non-sticky session variables. 845 Remove non-sticky session variables.
847 846
848 **Called by:** `restart()`, session validation failure 847 **Called by:** ##restart()##, session validation failure
849 848
850 **Preserves:** Variables starting with `sticky_` 849 **Preserves:** Variables starting with ##sticky_##
851 850
852 --- 851 ----
853 852
854 ##### `prevent_replay(): void` 853 ====== ##prevent_replay(): void## ======
855 Generate and send anti-replay nonce. 854 Generate and send anti-replay nonce.
856 855
857 **Called by:** `populate()` 856 **Called by:** ##populate()##
858 857
859 **Action:** 858 **Action:**
860 - Creates 'NoReplay' nonce 859   - Creates 'NoReplay' nonce
861 - Sends in cookie: `{cf_cookie_prefix}NoReplay` 860   - Sends in cookie: ##{cf_cookie_prefix}NoReplay##
862 861
863 --- 862 ----
864 863
865 ##### `cache_limiter(): void` 864 ====== ##cache_limiter(): void## ======
866 Set HTTP cache control headers based on configuration. 865 Set HTTP cache control headers based on configuration.
867 866
868 **Called by:** `start()` after session data loaded 867 **Called by:** ##start()## after session data loaded
869 868
870 **Modes:** 869 **Modes:**
871 - `'public'`: Cacheable, `Cache-Control: public, max-age=...` 870   - ##'public'##: Cacheable, ##Cache-Control: public, max-age=...##
872 - `'private'`: Private, `Cache-Control: private, max-age=...` 871   - ##'private'##: Private, ##Cache-Control: private, max-age=...##
873 - `'private_no_expire'`: Private no TTL 872   - ##'private_no_expire'##: Private no TTL
874 - `'nocache'`: No storage, `Cache-Control: no-store` 873   - ##'nocache'##: No storage, ##Cache-Control: no-store##
875 - `'none'`: No headers (default) 874   - ##'none'##: No headers (default)
876 875
877 --- 876 ----
878 877
879 ##### `set_new_id(): void` 878 ====== ##set_new_id(): void## ======
880 Generate and assign new session ID, send in cookie. 879 Generate and assign new session ID, send in cookie.
881 880
882 **Called by:** `regenerate_id()`, `start()` (for new sessions) 881 **Called by:** ##regenerate_id()##, ##start()## (for new sessions)
883 882
884 --- 883 ----
885 884
886 ##### `remove_cookie($cookie): void` 885 ====== ##remove_cookie($cookie): void## ======
887 Remove existing Set-Cookie header to avoid duplicates. 886 Remove existing Set-Cookie header to avoid duplicates.
888 887
889 **Called by:** `setcookie()` before setting new value 888 **Called by:** ##setcookie()## before setting new value
890 889
891 --- 890 ----
892 891
893 ##### `nonce_index($action, $code): string` (static) 892 ====== ##nonce_index($action, $code): string## (static) ======
894 Generate storage key for nonce. 893 Generate storage key for nonce.
895 894
896 **Returns:** `{action}.{base64_encoded_hash}` 895 **Returns:** ##{action}.{base64_encoded_hash}##
897 896
898 --- 897 ----
899 898
900 --- 899 ----
901 900
902 ## Session Lifecycle 901 === Session Lifecycle ===
903 902
904 ### Complete Session Flow 903 ==== Complete Session Flow ====
905 904
906 ``` 905 %%
907 ┌─ Browser Request 906 ┌─ Browser Request
908 907
909 ├─ Application Code 908 ├─ Application Code
956       │ └─ store_gc() (cf_gc_probability % chance) 955       │ └─ store_gc() (cf_gc_probability % chance)
957       │ └─ Delete old sessions 956       │ └─ Delete old sessions
958       └─ Output sent to browser 957       └─ Output sent to browser
959 ``` 958 %%
960 959
961 ### First Request (New Session) 960 ==== First Request (New Session) ====
962 961
963 ``` 962 %%
964 start() is called 963 start() is called
965 ├─ No ID in cookie 964 ├─ No ID in cookie
966 ├─ store_read(id) → false 965 ├─ store_read(id) → false
975 │ ├─ __user_agent = UA 974 │ ├─ __user_agent = UA
976 │ └─ sticky__created = now 975 │ └─ sticky__created = now
977 └─ return true 976 └─ return true
978 ``` 977 %%
979 978
980 ### Subsequent Request (Resume Session) 979 ==== Subsequent Request (Resume Session) ====
981 980
982 ``` 981 %%
983 start() is called 982 start() is called
984 ├─ ID from cookie 983 ├─ ID from cookie
985 ├─ store_read(id) → serialized_data 984 ├─ store_read(id) → serialized_data
992 │ ├─ UA/IP/TLS checks 991 │ ├─ UA/IP/TLS checks
993 │ └─ May trigger regenerate_id() 992 │ └─ May trigger regenerate_id()
994 └─ return true 993 └─ return true
995 ``` 994 %%
996 995
997 ### Session ID Regeneration 996 ==== Session ID Regeneration ====
998 997
999 ``` 998 %%
1000 regenerate_id($delete_old, $message) is called 999 regenerate_id($delete_old, $message) is called
1001 ├─ Check not headers_sent() 1000 ├─ Check not headers_sent()
1002 ├─ Check $active 1001 ├─ Check $active
1014 ├─ Set: regenerated = 1 1013 ├─ Set: regenerated = 1
1015 ├─ Log event: sticky__log[] = [now, message] 1014 ├─ Log event: sticky__log[] = [now, message]
1016 └─ return true 1015 └─ return true
1017 ``` 1016 %%
1018 1017
1019 ### Session Destruction 1018 ==== Session Destruction ====
1020 1019
1021 ``` 1020 %%
1022 Triggered by: 1021 Triggered by:
1023 ├─ restart() → regenerate_id(true) 1022 ├─ restart() → regenerate_id(true)
1024 ├─ Validation failure (destroy=2) 1023 ├─ Validation failure (destroy=2)
1030 ├─ Non-sticky variables cleared 1029 ├─ Non-sticky variables cleared
1031 ├─ sticky_ variables preserved 1030 ├─ sticky_ variables preserved
1032 └─ New session ID generated 1031 └─ New session ID generated
1033 ``` 1032 %%
1034 1033
1035 --- 1034 ----
1036 1035
1037 ## Flash Data 1036 === Flash Data ===
1038 1037
1039 Flash data persists for a limited number of requests (typically 1-2) and is automatically removed. 1038 Flash data persists for a limited number of requests (typically 1-2) and is automatically removed.
1040 1039
1041 ### Usage 1040 ==== Usage ====
1042 1041
1043 ```php 1042 %%(hl php)
1044 // Store flash message for next request 1043 // Store flash message for next request
1045 $session->set_flash('error', 'Username already exists', 1); // 1 request 1044 $session->set_flash('error', 'Username already exists', 1); // 1 request
1046 $session->set_flash('info', 'Welcome back!', 2); // 2 requests 1045 $session->set_flash('info', 'Welcome back!', 2); // 2 requests
1047 1046
1048 // In next request, data automatically available 1047 // In next request, data automatically available
1049 echo $session['error']; // "Username already exists" 1048 echo $session['error']; // "Username already exists"
1050 ``` 1049 %%
1051 1050
1052 ### How It Works 1051 ==== How It Works ====
1053 1052   1. **Storage:** Flash data stored in ##$session->sticky__flash##
1054 1. **Storage:** Flash data stored in `$session->sticky__flash` 1053   - Key: Variable name
1055    - Key: Variable name 1054   - Value: Lifetime in requests
1056    - Value: Lifetime in requests 1055   2. **Cleanup:** In ##terminator()## (shutdown handler):
1057 1056    %%(hl php)
1058 2. **Cleanup:** In `terminator()` (shutdown handler):    
1059    ```php    
1060    foreach ($sticky__flash as $var => $age) { 1057    foreach ($sticky__flash as $var => $age) {
1061        if (!isset($session[$var])) { 1058        if (!isset($session[$var])) {
1062            unset($sticky__flash[$var]); // Already deleted 1059            unset($sticky__flash[$var]); // Already deleted
1067            $flash__flash[$var] = $age; // Decrement counter 1064            $flash__flash[$var] = $age; // Decrement counter
1068        } 1065        }
1069    } 1066    }
1070    ``` 1067    %%
1071 1068   3. **Persistence:** Flash variables are kept in ##sticky__flash## even during session resets
1072 3. **Persistence:** Flash variables are kept in `sticky__flash` even during session resets 1069
1073 1070 ==== Example: Login Flow ====
1074 ### Example: Login Flow 1071
1075 1072 %%(hl php)
1076 ```php    
1077 // POST /login 1073 // POST /login
1078 if ($credentials_valid) { 1074 if ($credentials_valid) {
1079     $session->restart(); // New session 1075     $session->restart(); // New session
1092 if ($message = $session['success'] ?? null) { 1088 if ($message = $session['success'] ?? null) {
1093     echo "<div class='success'>$message</div>"; 1089     echo "<div class='success'>$message</div>";
1094 } 1090 }
1095 ``` 1091 %%
1096 1092
1097 --- 1093 ----
1098 1094
1099 ## Nonce System 1095 === Nonce System ===
1100 1096
1101 Nonces provide CSRF protection and replay attack detection. 1097 Nonces provide CSRF protection and replay attack detection.
1102 1098
1103 ### Terminology 1099 ==== Terminology ====
1104 1100   - **Nonce:** Number used ONCE - cryptographic token for action verification
1105 - **Nonce:** Number used ONCE - cryptographic token for action verification 1101   - **Action:** Type of operation being protected (e.g., 'form_submit', 'delete_user')
1106 - **Action:** Type of operation being protected (e.g., 'form_submit', 'delete_user') 1102   - **Protected Nonce:** Can be verified multiple times with protection against rapid reuse
1107 - **Protected Nonce:** Can be verified multiple times with protection against rapid reuse 1103
1108 1104 ==== Complete Example: Form Protection ====
1109 ### Complete Example: Form Protection 1105
1110 1106 %%(hl php)
1111 ```php    
1112 // 1. Display form with nonce 1107 // 1. Display form with nonce
1113 $nonce = $session->create_nonce('user_update', 3600); 1108 $nonce = $session->create_nonce('user_update', 3600);
1114 ?> 1109 ?>
1129     // Safe to process 1124     // Safe to process
1130     update_user($_POST); 1125     update_user($_POST);
1131 } 1126 }
1132 ``` 1127 %%
1133 1128
1134 ### Example: Protected Nonce (AJAX-Safe) 1129 ==== Example: Protected Nonce (AJAX-Safe) ====
1135 1130
1136 ```php 1131 %%(hl php)
1137 // Generate protected nonce (can verify multiple times) 1132 // Generate protected nonce (can verify multiple times)
1138 $nonce = $session->create_nonce('ajax_action', 300); 1133 $nonce = $session->create_nonce('ajax_action', 300);
1139 1134
1154     // Safe to process 1149     // Safe to process
1155     process_ajax(); 1150     process_ajax();
1156 } 1151 }
1157 ``` 1152 %%
1158 1153
1159 ### Nonce Storage Format 1154 ==== Nonce Storage Format ====
1160 1155
1161 ``` 1156 %%
1162 Internal storage (__nonces array): 1157 Internal storage (__nonces array):
1163 [ 1158 [
1164     "{action}.{hash}" => expiration_timestamp, 1159     "{action}.{hash}" => expiration_timestamp,
1169 - action: Custom action identifier 1164 - action: Custom action identifier
1170 - hash: First 11 chars of base64(sha1(code_bytes)) 1165 - hash: First 11 chars of base64(sha1(code_bytes))
1171 - expiration_timestamp: time() + lifetime 1166 - expiration_timestamp: time() + lifetime
1172 ``` 1167 %%
1173 1168
1174 ### Security Properties 1169 ==== Security Properties ====
1175 1170   - **CSRF Protection:** Nonce must match to process form
1176 - **CSRF Protection:** Nonce must match to process form 1171   - **One-Time Use:** Each nonce consumed after first verification (unless protected)
1177 - **One-Time Use:** Each nonce consumed after first verification (unless protected) 1172   - **Expiration:** Nonces automatically expire
1178 - **Expiration:** Nonces automatically expire 1173   - **Action-Specific:** Each action has separate nonce space
1179 - **Action-Specific:** Each action has separate nonce space 1174   - **AJAX-Safe:** Protected nonces allow multiple quick verifications
1180 - **AJAX-Safe:** Protected nonces allow multiple quick verifications 1175
1181 1176 ----
1182 --- 1177
1183 1178 === Cookie Management ===
1184 ## Cookie Management 1179
1185 1180 ==== Security Features ====
1186 ### Security Features 1181
1187 1182 The ##setcookie()## method implements comprehensive cookie security:
1188 The `setcookie()` method implements comprehensive cookie security: 1183
1189 1184 ===== Encoding =====
1190 #### Encoding 1185 %%(hl php)
1191 ```php    
1192 // Cookie names: RFC 2616 2.2 token format 1186 // Cookie names: RFC 2616 2.2 token format
1193 // Cookie values: RFC 6265 4.1.1 cookie-octet format 1187 // Cookie values: RFC 6265 4.1.1 cookie-octet format
1194 // Unsafe characters automatically URL-encoded 1188 // Unsafe characters automatically URL-encoded
1195 ``` 1189 %%
1196 1190
1197 #### Security Attributes 1191 ===== Security Attributes =====
1198 ```php 1192 %%(hl php)
1199 setcookie('auth', 'token', 1193 setcookie('auth', 'token',
1200     expires: time() + 3600, 1194     expires: time() + 3600,
1201     secure: true, // HTTPS only 1195     secure: true, // HTTPS only
1202     httponly: true, // Disable JavaScript 1196     httponly: true, // Disable JavaScript
1203     samesite: 'Strict' // CSRF protection 1197     samesite: 'Strict' // CSRF protection
1204 ); 1198 );
1205 ``` 1199 %%
1206 1200
1207 #### No Duplicate Headers 1201 ===== No Duplicate Headers =====
1208 ```php 1202 %%(hl php)
1209 // Automatically removes old Set-Cookie header before setting new one 1203 // Automatically removes old Set-Cookie header before setting new one
1210 // Prevents cookie header duplication 1204 // Prevents cookie header duplication
1211 remove_cookie($name) → clears old headers 1205 remove_cookie($name) → clears old headers
1212 setcookie() → sets new header 1206 setcookie() → sets new header
1213 ``` 1207 %%
1214 1208
1215 ### Configuration-Driven Defaults 1209 ==== Configuration-Driven Defaults ====
1216 1210
1217 ```php 1211 %%(hl php)
1218 $session->cf_cookie_path = '/app'; // Path 1212 $session->cf_cookie_path = '/app'; // Path
1219 $session->cf_cookie_domain = '.example.com'; // Domain 1213 $session->cf_cookie_domain = '.example.com'; // Domain
1220 $session->cf_cookie_secure = true; // HTTPS 1214 $session->cf_cookie_secure = true; // HTTPS
1224 1218
1225 $session->setcookie('token', 'value'); 1219 $session->setcookie('token', 'value');
1226 // Uses all configured defaults 1220 // Uses all configured defaults
1227 ``` 1221 %%
1228 1222
1229 ### Typical Secure Configuration 1223 ==== Typical Secure Configuration ====
1230 1224
1231 ```php 1225 %%(hl php)
1232 // Prevent XSS and CSRF 1226 // Prevent XSS and CSRF
1233 $session->cf_cookie_secure = true; // HTTPS only 1227 $session->cf_cookie_secure = true; // HTTPS only
1234 $session->cf_cookie_httponly = true; // No JavaScript access 1228 $session->cf_cookie_httponly = true; // No JavaScript access
1241 // Session cookies (delete on browser close) 1235 // Session cookies (delete on browser close)
1242 $session->cf_cookie_lifetime = 0; 1236 $session->cf_cookie_lifetime = 0;
1243 $session->cf_cookie_persistent = false; 1237 $session->cf_cookie_persistent = false;
1244 ``` 1238 %%
1245 1239
1246 --- 1240 ----
1247 1241
1248 ## Error Handling 1242 === Error Handling ===
1249 1243
1250 ### Graceful Degradation 1244 ==== Graceful Degradation ====
1251 1245
1252 The Session class gracefully handles errors: 1246 The Session class gracefully handles errors:
1253 1247
1254 #### Headers Already Sent 1248 ===== Headers Already Sent =====
1255 ```php 1249 %%(hl php)
1256 if (headers_sent($file, $line)) { 1250 if (headers_sent($file, $line)) {
1257     trigger_error("id regeneration requested after headers flushed at $file:$line", 1251     trigger_error("id regeneration requested after headers flushed at $file:$line",
1258                   E_USER_WARNING); 1252                   E_USER_WARNING);
1259     return false; 1253     return false;
1260 } 1254 }
1261 ``` 1255 %%
1262 1256
1263 **Impact:** Session ID cannot be regenerated, but session continues 1257 **Impact:** Session ID cannot be regenerated, but session continues
1264 1258
1265 #### Cookie Setting Failure 1259 ===== Cookie Setting Failure =====
1266 ```php 1260 %%(hl php)
1267 if (headers_sent($file, $line)) { 1261 if (headers_sent($file, $line)) {
1268     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",
1269                   E_USER_WARNING); 1263                   E_USER_WARNING);
1270     return; 1264     return;
1271 } 1265 }
1272 ``` 1266 %%
1273 1267
1274 **Impact:** Cookie not set, but session data remains accessible 1268 **Impact:** Cookie not set, but session data remains accessible
1275 1269
1276 #### Storage Errors 1270 ===== Storage Errors =====
1277 ```php 1271 %%(hl php)
1278 if ($this->store_read($this->id, true) !== '') { 1272 if ($this->store_read($this->id, true) !== '') {
1279     // error! [comment indicates error, but continues] 1273     // error! [comment indicates error, but continues]
1280 } 1274 }
1281 ``` 1275 %%
1282 1276
1283 **Impact:** Creates new session if storage returns error 1277 **Impact:** Creates new session if storage returns error
1284 1278
1285 ### Debug Logging 1279 ==== Debug Logging ====
1286 1280
1287 The Session class includes commented debug statements: 1281 The Session class includes commented debug statements:
1288 1282
1289 ```php 1283 %%(hl php)
1290 # Ut::dbg("regeneration failed by flush at $file:$line"); 1284 # Ut::dbg("regeneration failed by flush at $file:$line");
1291 # Ut::dbg($destroy, $message); 1285 # Ut::dbg($destroy, $message);
1292 # Ut::dbg("session setcookie $name failed by $file:$line"); 1286 # Ut::dbg("session setcookie $name failed by $file:$line");
1293 ``` 1287 %%
1294 1288
1295 To enable: Uncomment lines and ensure `Ut::dbg()` function exists 1289 To enable: Uncomment lines and ensure ##Ut::dbg()## function exists
1296 1290
1297 ### Event Logging 1291 ==== Event Logging ====
1298 1292
1299 Session events tracked in `sticky__log`: 1293 Session events tracked in ##sticky__log##:
1300 1294
1301 ```php 1295 %%(hl php)
1302 // Access session event history 1296 // Access session event history
1303 if (isset($session->sticky__log)) { 1297 if (isset($session->sticky__log)) {
1304     foreach ($session->sticky__log as [$timestamp, $message]) { 1298     foreach ($session->sticky__log as [$timestamp, $message]) {
1305         echo "[$timestamp] $message\n"; 1299         echo "[$timestamp] $message\n";
1306     } 1300     }
1307 } 1301 }
1308 ``` 1302 %%
1309 1303
1310 **Logged Events:** 1304 **Logged Events:**
1311 - Session regeneration (with reason) 1305   - Session regeneration (with reason)
1312 - Limited to 15 most recent events (old entries archived as '...') 1306   - Limited to 15 most recent events (old entries archived as '...')
1313 1307
1314 --- 1308 ----
1315 1309
1316 ## Implementation Guide 1310 === Implementation Guide ===
1317 1311
1318 ### Creating a Concrete Session Class 1312 ==== Creating a Concrete Session Class ====
1319 1313
1320 You must implement the abstract storage methods. Choose your storage backend: files, database, cache, etc. 1314 You must implement the abstract storage methods. Choose your storage backend: files, database, cache, etc.
1321 1315
1322 #### File-Based Storage 1316 ===== File-Based Storage =====
1323 1317
1324 ```php 1318 %%(hl php)
1325 <?php 1319 <?php
1326 1320
1327 class FileSession extends Session { 1321 class FileSession extends Session {
1378         } 1372         }
1379     } 1373     }
1380 } 1374 }
1381 ``` 1375 %%
1382 1376
1383 #### Database Storage (PDO) 1377 ===== Database Storage (PDO) =====
1384 1378
1385 ```php 1379 %%(hl php)
1386 <?php 1380 <?php
1387 1381
1388 class DatabaseSession extends Session { 1382 class DatabaseSession extends Session {
1446                    ->execute([$cutoff]); 1440                    ->execute([$cutoff]);
1447     } 1441     }
1448 } 1442 }
1449 ``` 1443 %%
1450 1444
1451 #### Redis Storage 1445 ===== Redis Storage =====
1452 1446
1453 ```php 1447 %%(hl php)
1454 <?php 1448 <?php
1455 1449
1456 class RedisSession extends Session { 1450 class RedisSession extends Session {
1496         // Redis handles expiration automatically with TTL 1490         // Redis handles expiration automatically with TTL
1497     } 1491     }
1498 } 1492 }
1499 ``` 1493 %%
1500 1494
1501 ### Complete Integration Example 1495 ==== Complete Integration Example ====
1502 1496
1503 ```php 1497 %%(hl php)
1504 <?php 1498 <?php
1505 1499
1506 // Initialize session with configuration 1500 // Initialize session with configuration
1546 } 1540 }
1547 1541
1548 // Automatic cleanup happens in register_shutdown_function() 1542 // Automatic cleanup happens in register_shutdown_function()
1549 ``` 1543 %%
1550 1544
1551 ### Configuration Best Practices 1545 ==== Configuration Best Practices ====
1552 1546
1553 ```php 1547 %%(hl php)
1554 <?php 1548 <?php
1555 1549
1556 class SessionConfig { 1550 class SessionConfig {
1587 $session = new FileSession(); 1581 $session = new FileSession();
1588 SessionConfig::apply($session, $_ENV['APP_ENV'] ?? 'production'); 1582 SessionConfig::apply($session, $_ENV['APP_ENV'] ?? 'production');
1589 $session->start('myapp'); 1583 $session->start('myapp');
1590 ``` 1584 %%
1591 1585
1592 ### Testing Tips 1586 ==== Testing Tips ====
1593 1587
1594 ```php 1588 %%(hl php)
1595 <?php 1589 <?php
1596 1590
1597 // Test nonce generation and verification 1591 // Test nonce generation and verification
1619 $session2 = new FileSession(); 1613 $session2 = new FileSession();
1620 $session2->start('myapp'); 1614 $session2->start('myapp');
1621 assert($session2['test_key'] === 'test_value'); 1615 assert($session2['test_key'] === 'test_value');
1622 ``` 1616 %%
1623 1617
1624 --- 1618 ----
1625 1619
1626 ## Security Checklist 1620 === Security Checklist ===
1627 1621
1628 Use this checklist when implementing sessions: 1622 Use this checklist when implementing sessions:
1629 1623   - [ ] Use HTTPS only in production
1630 - [ ] Use HTTPS only in production 1624   - [ ] Enable ##cf_cookie_secure##
1631 - [ ] Enable `cf_cookie_secure` 1625   - [ ] Enable ##cf_cookie_httponly##
1632 - [ ] Enable `cf_cookie_httponly` 1626   - [ ] Set ##cf_cookie_samesite## to 'Strict' or 'Lax'
1633 - [ ] Set `cf_cookie_samesite` to 'Strict' or 'Lax' 1627   - [ ] Set appropriate ##cf_max_session## timeout
1634 - [ ] Set appropriate `cf_max_session` timeout 1628   - [ ] Set appropriate ##cf_max_idle## timeout
1635 - [ ] Set appropriate `cf_max_idle` timeout 1629   - [ ] Enable ##cf_prevent_replay##
1636 - [ ] Enable `cf_prevent_replay` 1630   - [ ] Validate ##cf_ip## if possible
1637 - [ ] Validate `cf_ip` if possible 1631   - [ ] Validate ##cf_tls## on HTTPS sites
1638 - [ ] Validate `cf_tls` on HTTPS sites 1632   - [ ] Use nonces for all state-changing forms
1639 - [ ] Use nonces for all state-changing forms 1633   - [ ] Implement proper logout (call ##restart()##)
1640 - [ ] Implement proper logout (call `restart()`) 1634   - [ ] Regenerate on privilege escalation (login)
1641 - [ ] Regenerate on privilege escalation (login) 1635   - [ ] Monitor ##sticky__ip## for suspicious changes
1642 - [ ] Monitor `sticky__ip` for suspicious changes 1636   - [ ] Review ##sticky__log## for attack patterns
1643 - [ ] Review `sticky__log` for attack patterns 1637   - [ ] Implement garbage collection (##store_gc##)
1644 - [ ] Implement garbage collection (`store_gc`) 1638   - [ ] Hash session IDs before storing (see TODOs)
1645 - [ ] Hash session IDs before storing (see TODOs) 1639   - [ ] Use secure random token generation
1646 - [ ] Use secure random token generation 1640
1647 1641 ----
1648 --- 1642
1649 1643 === Common Patterns ===
1650 ## Common Patterns 1644
1651 1645 ==== Login Flow ====
1652 ### Login Flow 1646
1653 1647 %%(hl php)
1654 ```php    
1655 if ($_POST['action'] === 'login') { 1648 if ($_POST['action'] === 'login') {
1656     $user = authenticate($_POST['username'], $_POST['password']); 1649     $user = authenticate($_POST['username'], $_POST['password']);
1657     if ($user) { 1650     if ($user) {
1665         header('Location: /login'); 1658         header('Location: /login');
1666     } 1659     }
1667 } 1660 }
1668 ``` 1661 %%
1669 1662
1670 ### Logout Flow 1663 ==== Logout Flow ====
1671 1664
1672 ```php 1665 %%(hl php)
1673 if ($_GET['action'] === 'logout') { 1666 if ($_GET['action'] === 'logout') {
1674     $session->restart(); // Complete reset 1667     $session->restart(); // Complete reset
1675     header('Location: /'); 1668     header('Location: /');
1676 } 1669 }
1677 ``` 1670 %%
1678 1671
1679 ### CSRF-Protected Form 1672 ==== CSRF-Protected Form ====
1680 1673
1681 ```php 1674 %%(hl php)
1682 // Display form 1675 // Display form
1683 $csrf = $session->create_nonce('form_' . $form_id, 3600); 1676 $csrf = $session->create_nonce('form_' . $form_id, 3600);
1684 echo '<form method="POST">'; 1677 echo '<form method="POST">';
1693     } 1686     }
1694     // Process safely 1687     // Process safely
1695 } 1688 }
1696 ``` 1689 %%
1697 1690
1698 ### Permission Check with Session Regeneration 1691 ==== Permission Check with Session Regeneration ====
1699 1692
1700 ```php 1693 %%(hl php)
1701 if ($user->privilege_level < ADMIN_LEVEL && $promoted_to_admin) { 1694 if ($user->privilege_level < ADMIN_LEVEL && $promoted_to_admin) {
1702     $session->regenerate_id(false, 'privilege_escalation'); 1695     $session->regenerate_id(false, 'privilege_escalation');
1703     $session['is_admin'] = true; 1696     $session['is_admin'] = true;
1704 } 1697 }
1705 ``` 1698 %%
1706 1699
1707 ### Session Messages/Flash 1700 ==== Session Messages/Flash ====
1708 1701
1709 ```php 1702 %%(hl php)
1710 // After action 1703 // After action
1711 $session->set_flash('info', 'Profile updated successfully', 1); 1704 $session->set_flash('info', 'Profile updated successfully', 1);
1712 1705
1714 if (isset($session['info'])) { 1707 if (isset($session['info'])) {
1715     echo $session['info']; 1708     echo $session['info'];
1716 } 1709 }
1717 ``` 1710 %%
1718 1711
1719 --- 1712 ----
1720 1713
1721 ## Performance Considerations 1714 === Performance Considerations ===
1722 1715
1723 ### Optimization Tips 1716 ====Optimization Tips====
1724 1717   1. **Minimize Session Writes:**
1725 1. **Minimize Session Writes:** 1718     - Session data only written during ##write_close()## or regeneration
1726    - Session data only written during `write_close()` or regeneration 1719     - No unnecessary serialization during reads
1727    - No unnecessary serialization during reads 1720   2. **Garbage Collection:**
1728 1721     - Probabilistic GC (based on ##cf_gc_probability##)
1729 2. **Garbage Collection:** 1722     - Only runs on ~2% of requests by default
1730    - Probabilistic GC (based on `cf_gc_probability`) 1723     - Customize based on your session volume
1731    - Only runs on ~2% of requests by default 1724   3. **Nonce Cleanup:**
1732    - Customize based on your session volume 1725     - Expired nonces automatically removed on verification
1733 1726     - Verified nonces removed from storage
1734 3. **Nonce Cleanup:** 1727     - No manual cleanup needed
1735    - Expired nonces automatically removed on verification 1728   4. **Session ID Validation:**
1736    - Verified nonces removed from storage 1729     - Regex-based validation is fast
1737    - No manual cleanup needed 1730     - No database lookup needed
1738 1731   5. **Caching Strategy:**
1739 4. **Session ID Validation:** 1732     - Cache expensive lookups between session operations
1740    - Regex-based validation is fast 1733     - Session data loaded once per request
1741    - No database lookup needed 1734
1742 1735 ==== Benchmarks ====
1743 5. **Caching Strategy:**    
1744    - Cache expensive lookups between session operations    
1745    - Session data loaded once per request    
1746    
1747 ### Benchmarks    
1748 1736
1749 Typical performance on modern hardware: 1737 Typical performance on modern hardware:
1750 1738   - Session start: ~1-5ms (file) / ~2-10ms (database)
1751 - Session start: ~1-5ms (file) / ~2-10ms (database) 1739   - Session write: <1ms (file) / 1-5ms (database)
1752 - Session write: <1ms (file) / 1-5ms (database) 1740   - Nonce generation: <1ms
1753 - Nonce generation: <1ms 1741   - Nonce verification: <1ms
1754 - Nonce verification: <1ms 1742
1755 1743 ----
1756 --- 1744
1757 1745 === Troubleshooting ===
1758 ## Troubleshooting 1746
1759 1747 ==== Session Not Starting ====
1760 ### Session Not Starting 1748
1761 1749 %%(hl php)
1762 ```php    
1763 if (!$session->start('myapp')) { 1750 if (!$session->start('myapp')) {
1764     // Check reasons: 1751     // Check reasons:
1765     // 1. Headers already sent? 1752     // 1. Headers already sent?
1767     // 3. Permissions issue on session directory? 1754     // 3. Permissions issue on session directory?
1768     debug_backtrace(); 1755     debug_backtrace();
1769 } 1756 }
1770 ``` 1757 %%
1771 1758
1772 ### Cookie Not Setting 1759 ==== Cookie Not Setting ====
1773 1760
1774 ```php 1761 %%(hl php)
1775 // If setcookie() returns false: 1762 // If setcookie() returns false:
1776 // - Check if headers_sent() 1763 // - Check if headers_sent()
1777 // - Check if cookie name is RFC 2616 compliant 1764 // - Check if cookie name is RFC 2616 compliant
1778 // - Check if cookie value is properly encoded 1765 // - Check if cookie value is properly encoded
1779 ``` 1766 %%
1780 1767
1781 ### Session ID Not Regenerating 1768 ==== Session ID Not Regenerating ====
1782 1769
1783 ```php 1770 %%(hl php)
1784 // If regenerate_id() returns false: 1771 // If regenerate_id() returns false:
1785 // - Headers might be sent 1772 // - Headers might be sent
1786 // - $active might be false 1773 // - $active might be false
1788 if (!$session->regenerate_id()) { 1775 if (!$session->regenerate_id()) {
1789     error_log("Regeneration failed: headers sent or session inactive"); 1776     error_log("Regeneration failed: headers sent or session inactive");
1790 } 1777 }
1791 ``` 1778 %%
1792 1779
1793 ### Nonce Verification Failing 1780 ==== Nonce Verification Failing ====
1794 1781
1795 ```php 1782 %%(hl php)
1796 // If verify_nonce() returns false: 1783 // If verify_nonce() returns false:
1797 // 1. Nonce might be expired 1784 // 1. Nonce might be expired
1798 // 2. Nonce might be for different action 1785 // 2. Nonce might be for different action
1801 1788
1802 // Debug: 1789 // Debug:
1803 var_dump($session->__nonces); // See stored nonces 1790 var_dump($session->__nonces); // See stored nonces
1804 ``` 1791 %%
1805 1792
1806 ### Session Data Lost 1793 ==== Session Data Lost ====
1807 1794
1808 ```php 1795 %%(hl php)
1809 // Possible causes: 1796 // Possible causes:
1810 // 1. write_close() not called (usually automatic via shutdown) 1797 // 1. write_close() not called (usually automatic via shutdown)
1811 // 2. Storage backend failing silently 1798 // 2. Storage backend failing silently
1816 if ($message = $session->message()) { 1803 if ($message = $session->message()) {
1817     error_log("Session issue: $message"); 1804     error_log("Session issue: $message");
1818 } 1805 }
1819 ``` 1806 %%
1820 1807
1821 --- 1808 ----
1822 1809
1823 ## TODO Items (From Code Comments) 1810 ===TODO Items (From Code Comments)===
1824    
1825 The following improvements are planned: 1811 The following improvements are planned:
1826 1812   1. **Do not store session ID in filename or DB index - store hash instead**
1827 1. **Do not store session ID in filename or DB index - store hash instead** 1813     - Improves security by not exposing IDs in storage layer
1828    - Improves security by not exposing IDs in storage layer 1814     - Would require hashing logic in store_* methods
1829    - Would require hashing logic in store_* methods 1815   2. **Log of IP changes and other possible security alerts**
1830 1816     - Track ##sticky__ip## changes more comprehensively
1831 2. **Log of IP changes and other possible security alerts** 1817     - Create security audit trail
1832    - Track `sticky__ip` changes more comprehensively 1818   3. **Allocate internal unique session which lives through lifetime of uber-session**
1833    - Create security audit trail 1819     - Multi-session management (parent/child sessions)
1834 1820     - Useful for complex user flows
1835 3. **Allocate internal unique session which lives through lifetime of uber-session** 1821   4. **Do not delete old sessions, but use them as hijack pointers**
1836    - Multi-session management (parent/child sessions) 1822     - Maintain session history for analysis
1837    - Useful for complex user flows 1823     - Detect potential session hijacking patterns
1838 1824     - Implement session relationship tracking
1839 4. **Do not delete old sessions, but use them as hijack pointers** 1825   5. **All SIDs used later than ~5secs of regenerations is hijacks**
1840    - Maintain session history for analysis 1826     - Detect and block delayed session ID usage
1841    - Detect potential session hijacking patterns 1827     - Current implementation allows 5-second window
1842    - Implement session relationship tracking 1828     - Could be more granular
1843 1829
1844 5. **All SIDs used later than ~5secs of regenerations is hijacks** 1830 ----
1845    - Detect and block delayed session ID usage 1831
1846    - Current implementation allows 5-second window 1832 === References ===
1847    - Could be more granular 1833
1848 1834 ==== Security Standards ====
1849 --- 1835   - RFC 2616: HTTP/1.1 (Cookie syntax)
1850 1836   - RFC 6265: HTTP State Management Mechanism
1851 ## References 1837   - RFC 6234: US Secure Hash and Message Authentication Code Algorithms
1852 1838   - OWASP: Session Management Cheat Sheet
1853 ### Security Standards 1839   - OWASP: Cross-Site Request Forgery (CSRF) Prevention
1854 1840
1855 - RFC 2616: HTTP/1.1 (Cookie syntax) 1841 ==== Related Code ====
1856 - RFC 6265: HTTP State Management Mechanism 1842   - ##Ut::serialize()## / ##Ut::unserialize()##: Session data serialization
1857 - RFC 6234: US Secure Hash and Message Authentication Code Algorithms 1843   - ##Ut::random_token()##: Cryptographic token generation
1858 - OWASP: Session Management Cheat Sheet 1844   - ##Ut::http_date()##: HTTP date formatting
1859 - OWASP: Cross-Site Request Forgery (CSRF) Prevention 1845   - ##Ut::urlencode()##: Cookie-safe encoding
1860 1846   - ##Ut::is_empty()##: Empty value checking
1861 ### Related Code 1847
1862 1848 ==== See Also ====
1863 - `Ut::serialize()` / `Ut::unserialize()`: Session data serialization 1849   - ##src/class/http.php##: HTTP request/response handling
1864 - `Ut::random_token()`: Cryptographic token generation 1850   - ##src/class/auth.php##: Authentication (uses Session)
1865 - `Ut::http_date()`: HTTP date formatting 1851   - Session security best practices in OWASP documentation
1866 - `Ut::urlencode()`: Cookie-safe encoding 1852
1867 - `Ut::is_empty()`: Empty value checking 1853 ----
1868 1854
1869 ### See Also 1855 ===Version History===
1870 1856   - **Current**: Abstract session class with security features
1871 - `src/class/http.php`: HTTP request/response handling 1857   - **Planned**: Implementation of TODO items above
1872 - `src/class/auth.php`: Authentication (uses Session) 1858
1873 - Session security best practices in OWASP documentation 1859 ----
1874 1860 **Documentation generated: 2026-05-05**
1875 --- 1861 **For latest updates, see: https://github.com/Trojer/wackowiki/blob/main/docs/SESSION_DOCUMENTATION.md**
1876    
1877 ## Version History    
1878    
1879 - **Current**: Abstract session class with security features    
1880 - **Planned**: Implementation of TODO items above    
1881    
1882 ---    
1883    
1884 *Documentation generated: 2026-05-05*    
1885 *For latest updates, see: https://github.com/Trojer/wackowiki/blob/main/docs/SESSION_DOCUMENTATION.md*