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