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.
Location:src/class/session.php Type: Abstract class (must be extended with a SessionStoreInterface implementation) Inheritance:ArrayObject
Inactive ($active = false): Session not yet started or has been closed
Active ($active = true): Session is running and can store/retrieve data
Regenerated: Session ID has been replaced (tracked via $regenerated flag)
Session Data Storage
Session data is stored as an array accessible through ArrayObject interface:
php<!--markup:1:end-->
<!--markup:2:begin-->
(hl php)<!markup:2:end>
$session['user_id'] = 123; // Set data
echo $session['user_id']; // Get data
==== Sticky Data ====
Variables prefixed with ##sticky_## are persistent across session resets:
- ##sticky__created##: Session creation timestamp
- ##sticky__flash##: Flash data lifetime tracking
- ##sticky__log##: Regeneration event log
- ##sticky__ip##: IP change tracking
==== Internal Tracking Variables ====
Variables prefixed with ##__## are internal session metadata:
- ##__started##: Session start time
- ##__updated##: Last session update time
- ##__regenerated##: Last session ID regeneration time
- ##__user_agent##: Client user agent string
- ##__user_ip##: Client IP address
- ##__user_tls##: TLS/SSL status
- ##__nonces##: Active nonce storage
- ##__expire##: Session expiration time (for old sessions)
----
=== Architecture ===
==== Class Hierarchy ====
<!--markup:2:end-->// Create a concrete session implementationclassMySessionextendsSession{// Implement abstract store_* methods// See "Implementation Guide" section}// Initialize and start session$session=newMySession();$session->cf_max_session=36;// 1 hour$session->cf_cookie_path='/';$session->start('myapp');// Session name: 'myapp'// Store data$session['user_id']=42;$session['username']='john';// Retrieve dataecho$session['user_id'];// 42// Check if session is activeif($session->active()){echo"Session is active";}// Explicitly save and close$session->write_close();// Shutdown handler automatically called via register_shutdown_function()
Session Data Access
PHP
// Array-like access (via ArrayObject)$session['user_id']=123;echo$session['user_id'];unset($session['user_id']);isset($session['user_id']);// Convert to array$all_data=$session->toArray();
Session ID Management
<!markup:1:begin>
php<!--markup:1:end-->
<!--markup:2:begin-->
(hl php)
// Get current session ID
$id = $session->id(); // Returns: e.g., "abc123xyz..."
// Get session name
$name = $session->name(); // Returns: 'myapp'
// Get session ID from request
$session->start('myapp', $_REQUEST['sid'] ?? null);
==== Session State ====
<!--markup:1:begin-->
php<!markup:1:end>
PHP
<!--markup:2:end-->// Check if session is activeif($session->active()){// Session is running}// Get last state change message$message=$session->message();// 'replay', 'ip', 'ua', 'timeout', etc.// Restart session (destroy old + start new)$session->restart();
Security Features
1. Session ID Regeneration
Purpose: Prevent session fixation attacks
Automatic Triggers:
Initial session creation (regenerated = 2)
First request after creation (regenerated = 1)
Periodic forced regeneration (based on cf_regen_time and cf_regen_probability)
false (): Keep old session active for 5 seconds (for pending AJAX requests)
true (1): Keep old session for time specified (unused in current code)
2: Immediately destroy old session
Implementation Details:
New session ID is generated via store_generate_id()
Old session data is copied to new ID
Old session marked with __expire timestamp
Cookie immediately updated with new ID
Single regeneration per request (checked via $this->regenerated flag)
Logged in sticky__log for debugging (max 15 entries)
PHP
// Example: Force regeneration on login$session->start('myapp');if($user_authenticated){$session->regenerate_id(false,'login');$session['user_id']=$user->id;}
2. User Agent Validation
Purpose: Detect browser/device changes that might indicate hijacking
Behavior:
Stores user agent on first request
Compares on subsequent requests using similar_text()
Destroys session if similarity < 95%
Useful against bot attacks or stolen sessions
Configuration:
<!markup:1:begin>
php<!--markup:1:end-->**
<!--markup:2:begin-->
(hl php)
// Automatic on each request (if enabled in code logic)
// Triggers session destruction if UA changes significantly
==== 3. IP Address Validation ====
**Purpose:** Detect IP spoofing or hijacking
**Behavior:**
- Stores IP on first request
- Compares on subsequent requests
- Soft failure on mismatch: ##destroy = 1## (keeps regenerating)
- Tracks IP changes in ##sticky__ip##
**Configuration:**
<!--markup:1:begin-->
php<!markup:1:end>** PHP
<!--markup:2:end-->$session->cf_ip=$_SERVER['REMOTE_ADDR'];// Set by HTTP class// Validation happens automatically during start()
IP Change Tracking:
<!markup:1:begin>
php<!--markup:1:end-->**
<!--markup:2:begin-->
(hl php)
// Access IP change history
$ip_history = $session->sticky__ip; // Array of [ip => change_count]
==== 4. TLS/SSL Validation ====
**Purpose:** Prevent protocol downgrade attacks
**Behavior:**
- Checks if connection transitioned from HTTPS to HTTP
- Destroys session on mismatch
**Configuration:**
(hl php)
$session->cf_tls = !empty($_SERVER['HTTPS']); // Set by HTTP class
// Validation happens automatically during start()
Request 1: Generate nonce, send in cookie
Request 2: Client sends nonce back, verify & generate new one
Request 3: If old nonce used again → reject (replay detected)
<!--markup:2:end-->$session->cf_referer_check='example.com';// Session rejected if HTTP_REFERER doesn't contain this string
API Reference
Public Methods
Lifecycle Management
start($name = null, $id = null): bool
Start or resume a session.
Parameters:
$name (string|null): Session name (cookie name base). Alphanumeric + underscore/dash. Defaults to 'sesid'
$id (string|null): Existing session ID to resume. If null, attempts to read from cookie
Returns:true if session started successfully, false on error
Side Effects:
Sets headers (cookies, cache control)
Populates session data from storage
Performs security validations
May trigger session ID regeneration
Example:
<!markup:1:begin>
php<!--markup:1:end-->**
<!--markup:2:begin-->
(hl php)
if ($session->start('webapp', $_COOKIE['sess_id'] ?? null)) {
// Session ready
} else {
// Session failed
}
**Validation Steps:**
1. Reject if headers already sent
2. Validate session name format
3. Retrieve ID from parameter or cookie
4. Check Referer header (if configured)
5. Validate ID format via ##store_validate_id()##
6. Read session data from storage
7. Verify nonces and timestamps
8. Check user agent, IP, TLS
9. Regenerate if needed
----
====== ##write_close(): void## ======
Save session data and close session.
**Side Effects:**
- Calls ##write_session()## to serialize and store data
- Calls ##store_close()## to close storage handler
- Sets ##$active = false##
**Example:**
(hl php)
$session['key'] = 'value';
$session->write_close(); // Ensure data is saved
----
====== ##restart(): bool## ======
Destroy current session and create new one.
**Equivalent to:** ##regenerate_id(true) + clean_vars() + populate()##
**Returns:** ##true## on success, ##false## on error
**Use Cases:**
- User logout and new login
- Security reset
- Complete session refresh
**Example:**
<!--markup:1:begin-->
php<!markup:1:end>** PHP
<!--markup:2:end-->$session->restart();// New session created, old data cleared, sticky_ vars preserved
Session Access
id(): mixed
Get current session ID.
Returns: Session ID string or null if not started
PHP
$sid=$session->id();// "abc123xyz..."
name(): string
Get session name (cookie prefix).
Returns: Session name
<!markup:1:begin>
php<!--markup:1:end-->
<!--markup:2:begin-->
(hl php)
$name = $session->name(); // "myapp"
----
====== ##active(): bool## ======
Check if session is currently active.
**Returns:** ##true## if session is started and active, ##false## otherwise
(hl php)
if ($session->active()) {
$session['key'] = 'value';
}
----
====== ##message(): string|null## ======
Get reason for last session state change.
**Returns:** Message string or null
**Possible Values:**
- ##'replay'##: Replay attack detected
- ##'obsolete'##: Session marked for expiration
- ##'reg_expire'##: Regeneration expiration reached
- ##'max_session'##: Max session lifetime exceeded
- ##'max_idle'##: Idle timeout exceeded
- ##'ua'##: User agent mismatch (>5% difference)
- ##'tls'##: TLS status changed
- ##'ip'##: IP address mismatch
- ##'restart'##: Session manually restarted
- ##null##: No state change
**Example:**
(hl php)
$session->start('app');
if ($message = $session->message()) {
error_log("Session issue: $message");
}
----
====== ##toArray(): array## ======
Convert session data to array.
**Returns:** Associative array of session data
**Note:** This is a direct call to ##ArrayObject::getArrayCopy()##
<!--markup:1:begin-->
$expires (int|null): Expiration time in seconds. Defaults to cf_nonce_lifetime
Returns: Nonce token string (11 characters)
Example: PHP
$nonce=$session->create_nonce('form_submit',36);// Use in HTML: <input type="hidden" name="nonce" value="<?= $nonce ?>">
Storage:
Stored in $session->__nonces[]
Key: {action}.{base64_encoded_hash}
Value: Expiration timestamp
verify_nonce($action, $code, $protect = )
Verify a nonce token.
Parameters:
$action (string): Action identifier that was used in create_nonce()
$code (string): Nonce token from user
$protect (int): Protection level
: Single-use nonce (consumed on first verification)
1+: Protected nonce (can verify multiple times, prevents fast replays)
Returns:
true (1): Nonce verified and valid
false (): Nonce invalid or expired
-1: Protected nonce used twice in quick succession (possible AJAX attack)
Example: PHP
if($nonce=$session->verify_nonce('form_submit',$_POST['nonce'])){if($nonce===-1){// Possible replay, but might be legitimate AJAX$session->cf_prevent_replay=;// Disable for this request}else{// Safe to processprocess_form();}}
Alias for setcookie($name) with no value (convenience method).
<!markup:1:begin>
php<!--markup:1:end-->
<!--markup:2:begin-->
(hl php)
$session->unsetcookie('cookie_name');
----
==== Protected Methods (For Store Implementation) ====
===== ##regenerate_id($delete_old = false, $message = ''): bool## =====
Internal method to regenerate session ID (called automatically).
**Protected** - Usually called automatically, but can be overridden/called by subclasses
----
===== ##store_generate_id(): string## =====
Generate a new session ID.
**Default Implementation:** Returns 21-character random alphanumeric string via ##Ut::random_token(21)##
**Override in subclass to customize:**
<!--markup:1:begin-->
php<!markup:1:end>** PHP
<!--markup:2:end-->protectedfunctionstore_generate_id():string{returnhash('sha256',random_bytes(32));// Your format}
----
===== ##store_write($id, $data): void## =====
Write session data to storage.
**Subclass must implement**
**Parameters:**
- ##$id##: Session ID
- ##$data##: Serialized session data (already processed by ##Ut::serialize()##)
**Example:**
(hl php)
protected function store_write($id, $data): void {
file_put_contents("/tmp/sess_$id", $data);
}
----
===== ##store_close(): void## =====
Close session storage.
**Subclass must implement** - Release resources
**Example:**
<!--markup:1:begin-->
php<!markup:1:end>** PHP
<!--markup:2:end-->protectedfunctionstore_close():void{// Close database, file, etc.}
store_gc(): void
Perform garbage collection on old sessions.
Subclass must implement - Delete expired sessions
Called During:
Shutdown handler (probabilistic, based on cf_gc_probability)
Should Delete:
Sessions older than cf_gc_maxlifetime seconds
Example: PHP
protectedfunctionstore_gc():void{$max_age=time()-$this->cf_gc_maxlifetime;// Delete files/records older than $max_age}
Private Methods (Internal Use)
populate(): void
Initialize session tracking variables on first request.
Called by:start(), restart()
Initializes:
__started: Current timestamp
__regenerated: Current timestamp
__user_agent: Browser user agent
__user_ip: Client IP (if configured)
__user_tls: TLS status (if configured)
sticky__created: Creation time (if not exists)
write_session(): void
Serialize and write session data to storage.
Called by:regenerate_id(), write_close(), terminator()
Updates:
__updated: Current timestamp
Calls store_write() with serialized data
clean_vars(): void
Remove non-sticky session variables.
Called by:restart(), session validation failure
Preserves: Variables starting with sticky_
prevent_replay(): void
Generate and send anti-replay nonce.
Called by:populate()
Action:
Creates 'NoReplay' nonce
Sends in cookie: {cf_cookie_prefix}NoReplay
cache_limiter(): void
Set HTTP cache control headers based on configuration.
Generate and assign new session ID, send in cookie.
Called by:regenerate_id(), start() (for new sessions)
remove_cookie($cookie): void
Remove existing Set-Cookie header to avoid duplicates.
Called by:setcookie() before setting new value
nonce_index($action, $code): string (static)
Generate storage key for nonce.
Returns:{action}.{base64_encoded_hash}
Session Lifecycle
Complete Session Flow
┌─ Browser Request
│
├─ Application Code
│ └─ $session->start('appname')
│ │
│ ├─ Check if headers sent
│ ├─ Validate/read session name
│ ├─ Get session ID from:
│ │ 1. Parameter $id
│ │ 2. Cookie: {prefix}appname
│ ├─ Validate referer (if cf_referer_check set)
│ ├─ Validate ID format via store_validate_id()
│ ├─ store_open(name)
│ ├─ store_read(id)
│ │ └─ If missing/invalid/expired:
│ │ └─ set_new_id()
│ │ └─ regenerate_id = 2 (NEW)
│ ├─ Deserialize session data
│ ├─ exchangeArray(data)
│ ├─ active = true
│ ├─ cache_limiter()
│ │
│ └─ Security Checks (if NOT first request):
│ ├─ Verify NoReplay nonce
│ ├─ Check expiration flags
│ ├─ Check max session time
│ ├─ Check max idle time
│ ├─ Compare user agent (95%+ similarity)
│ ├─ Compare TLS status
│ ├─ Compare IP address
│ │ ├─ Match: OK
│ │ └─ Mismatch: destroy=1, regenerate
│ └─ Check regen time/probability
│ └─ regenerate_id()
│
├─ Application Code
│ └─ $session['key'] = 'value'
│
└─ End of Request
│
└─ register_shutdown_function() → terminator()
│
├─ Process flash data
│ └─ Decrement lifetimes
│ └─ Remove expired flash
├─ write_session()
│ └─ store_write(id, serialized_data)
├─ store_close()
├─ Probabilistic garbage collection
│ └─ store_gc() (cf_gc_probability % chance)
│ └─ Delete old sessions
└─ Output sent to browser
First Request (New Session)
start() is called
├─ No ID in cookie
├─ store_read(id) → false
├─ set_new_id()
│ └─ id = store_generate_id()
│ └─ send_cookie(name, id)
├─ data = []
├─ active = true
├─ populate()
│ ├─ __started = now
│ ├─ __regenerated = now
│ ├─ __user_agent = UA
│ └─ sticky__created = now
└─ return true
Subsequent Request (Resume Session)
start() is called
├─ ID from cookie
├─ store_read(id) → serialized_data
├─ data = unserialize(data)
├─ exchangeArray(data)
├─ active = true
├─ Security checks:
│ ├─ Replay check
│ ├─ Timeout checks
│ ├─ UA/IP/TLS checks
│ └─ May trigger regenerate_id()
└─ return true
Session ID Regeneration
regenerate_id($delete_old, $message) is called
├─ Check not headers_sent()
├─ Check $active
├─ Check not already regenerated in this request
├─ write_session() [Save current data]
├─ set __expire:
│ ├─ if $delete_old=: __expire = now + 5
│ └─ if $delete_old>: __expire =
├─ Generate new ID:
│ └─ loop:
│ ├─ id = store_generate_id()
│ └─ while store_read(id) !== false [Ensure unique]
├─ Lock new session: store_read(id, true)
├─ Set: __regenerated = now
├─ Set: regenerated = 1
├─ Log event: sticky__log[] = [now, message]
└─ return true
Flash data persists for a limited number of requests (typically 1-2) and is automatically removed.
Usage
PHP
// Store flash message for next request$session->set_flash('error','Username already exists',1);// 1 request$session->set_flash('info','Welcome back!',2);// 2 requests// In next request, data automatically availableecho$session['error'];// "Username already exists"
How It Works
Storage: Flash data stored in $session->sticky__flash
Key: Variable name
Value: Lifetime in requests
Cleanup: In terminator() (shutdown handler):
<!markup:1:begin>
php<!--markup:1:end-->
<!--markup:2:begin-->
(hl php)
foreach ($sticky__flash as $var => $age) {
3. **Persistence:** Flash variables are kept in ##sticky__flash## even during session resets
==== Example: Login Flow ====
<!--markup:1:begin-->
php<!markup:1:end-->
PHP
<!--markup:2:end-->// POST /loginif($credentials_valid){$session->restart();// New session$session['user_id']=$user->id;$session->set_flash('success','Login successful!',1);header('Location: /dashboard');}else{$session->set_flash('error','Invalid credentials',1);header('Location: /login');}// GET /dashboard (or /login on failure)if($message=$session['error']??null){echo"<div class='error'>$message</div>";}if($message=$session['success']??null){echo"<div class='success'>$message</div>";}
Nonce System
Nonces provide CSRF protection and replay attack detection.
Terminology
Nonce: Number used ONCE - cryptographic token for action verification
Action: Type of operation being protected (e.g., 'form_submit', 'delete_user')
Protected Nonce: Can be verified multiple times with protection against rapid reuse
Complete Example: Form Protection
<!markup:1:begin>
php<!--markup:1:end-->
<!--markup:2:begin-->
(hl php)
// 1. Display form with nonce
$nonce = $session->create_nonce('user_update', 36);
?>
<form method="POST" action="/update-profile">
// Automatically removes old Set-Cookie header before setting new one// Prevents cookie header duplicationremove_cookie($name)→clearsoldheaderssetcookie()→setsnewheader
Configuration-Driven Defaults
PHP
$session->cf_cookie_path='/app';// Path$session->cf_cookie_domain='.example.com';// Domain$session->cf_cookie_secure=true;// HTTPS$session->cf_cookie_httponly=true;// No JS$session->cf_cookie_samesite='Lax';// SameSite$session->cf_cookie_prefix='app_';// Prefix$session->setcookie('token','value');// Uses all configured defaults
Typical Secure Configuration
PHP
// Prevent XSS and CSRF$session->cf_cookie_secure=true;// HTTPS only$session->cf_cookie_httponly=true;// No JavaScript access$session->cf_cookie_samesite='Strict';// Strict CSRF protection// Set scope$session->cf_cookie_path='/';// Root path$session->cf_cookie_domain='';// Current host only// Session cookies (delete on browser close)$session->cf_cookie_lifetime=;$session->cf_cookie_persistent=false;
Error Handling
Graceful Degradation
The Session class gracefully handles errors:
Headers Already Sent
PHP
if(headers_sent($file,$line)){trigger_error("id regeneration requested after headers flushed at $file:$line",E_USER_WARNING);returnfalse;}
Impact: Session ID cannot be regenerated, but session continues
Cookie Setting Failure
PHP
if(headers_sent($file,$line)){trigger_error("cannot place session cookie $name=$valuedue to $file:$line",E_USER_WARNING);return;}
Impact: Cookie not set, but session data remains accessible
Storage Errors
PHP
if($this->store_read($this->id,true)!==''){// error! [comment indicates error, but continues]}
Impact: Creates new session if storage returns error
Debug Logging
The Session class includes commented debug statements:
PHP
# Ut::dbg("regeneration failed by flush at $file:$line");# Ut::dbg($destroy, $message);# Ut::dbg("session setcookie $name failed by $file:$line");
To enable: Uncomment lines and ensure Ut::dbg() function exists
Event Logging
Session events tracked in sticky__log:
<!markup:1:begin>
php<!--markup:1:end-->
<!--markup:2:begin-->
(hl php)
// Access session event history
if (isset($session->sticky__log)) {
foreach ($session->sticky__log as [$timestamp, $message]) {
echo "[$timestamp] $message\n";
}
}
**Logged Events:**
- Session regeneration (with reason)
- Limited to 15 most recent events (old entries archived as '...')
----
=== Implementation Guide ===
==== Creating a Concrete Session Class ====
You must implement the abstract storage methods. Choose your storage backend: files, database, cache, etc.
===== File-Based Storage =====
id VARCHAR(21) PRIMARY KEY,
data LONGTEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)
SQL;
$this->pdo->exec($sql);
}
protected function store_open($name): void {
// Database already connected
}
protected function store_read($id, $lock = false): string|false {
$stmt = $this->pdo->prepare('SELECT data FROM sessions WHERE id = ?');
$stmt->execute([$id]);
if ($result = $stmt->fetch(PDO::FETCH_ASSOC)) {
return $result['data'];
}
if ($lock) {
// Create new session
$stmt = $this->pdo->prepare('INSERT INTO sessions (id, data) VALUES (?, ?)');
$stmt->execute([$id, '']);
return '';
}
return false;
}
protected function store_write($id, $data): void {
$stmt = $this->pdo->prepare(
'INSERT INTO sessions (id, data) VALUES (?, ?)
ON DUPLICATE KEY UPDATE data = VALUES(data)'
);
$stmt->execute([$id, $data]);
}
protected function store_close(): void {
// Connection persists for application
}
protected function store_gc(): void {
$cutoff = time() - $this->cf_gc_maxlifetime;
$this->pdo->prepare('DELETE FROM sessions WHERE updated_at < FROM_UNIXTIME(?)')
->execute([$cutoff]);
}
}
===== Redis Storage =====
<!--markup:1:begin-->
php<!markup:1:end>
PHP
<!--markup:2:end--><?phpclassRedisSessionextendsSession{privateRedis$redis;privatestring$prefix='sess:';publicfunction__construct(Redis$redis){parent::__construct();$this->redis=$redis;}protectedfunctionstore_open($name):void{// Redis already connected}protectedfunctionstore_read($id,$lock=false):string|false{$data=$this->redis->get($this->prefix.$id);if($data!==false){return$data;}if($lock){// Create new session$this->redis->set($this->prefix.$id,'',['EX'=>$this->cf_gc_maxlifetime]);return'';}returnfalse;}protectedfunctionstore_write($id,$data):void{$this->redis->set($this->prefix.$id,$data,['EX'=>$this->cf_gc_maxlifetime]);}protectedfunctionstore_close():void{// Connection persists}protectedfunctionstore_gc():void{// Redis handles expiration automatically with TTL}}
Complete Integration Example
<!markup:1:begin>
php<!--markup:1:end-->
<!--markup:2:begin-->
(hl php)
<?php
// Initialize session with configuration
$session = new FileSession();
// Test user agent validation
assert(isset($session->__user_agent));
// Test session ID format
assert(preg_match('/^[a-zA-Z-9]{21}$/', $session->id()));
// Test data persistence
$session['test_key'] = 'test_value';
$session->write_close();
// New request...
$session2 = new FileSession();
$session2->start('myapp');
assert($session2['test_key'] === 'test_value');
----
=== Security Checklist ===
Use this checklist when implementing sessions:
- [ ] Use HTTPS only in production
- [ ] Enable ##cf_cookie_secure##
- [ ] Enable ##cf_cookie_httponly##
- [ ] Set ##cf_cookie_samesite## to 'Strict' or 'Lax'
- [ ] Set appropriate ##cf_max_session## timeout
- [ ] Set appropriate ##cf_max_idle## timeout
- [ ] Enable ##cf_prevent_replay##
- [ ] Validate ##cf_ip## if possible
- [ ] Validate ##cf_tls## on HTTPS sites
- [ ] Use nonces for all state-changing forms
- [ ] Implement proper logout (call ##restart()##)
- [ ] Regenerate on privilege escalation (login)
- [ ] Monitor ##sticky__ip## for suspicious changes
- [ ] Review ##sticky__log## for attack patterns
- [ ] Implement garbage collection (##store_gc##)
- [ ] Hash session IDs before storing (see TODOs)
- [ ] Use secure random token generation
----
=== Common Patterns ===
==== Login Flow ====
<!--markup:1:begin-->
php<!markup:1:end>
PHP
<!--markup:2:end-->if($_POST['action']==='login'){$user=authenticate($_POST['username'],$_POST['password']);if($user){$session->regenerate_id(false,'login');// New ID after auth$session['user_id']=$user->id;$session['username']=$user->username;$session['roles']=$user->roles;header('Location: /dashboard');}else{$session->set_flash('error','Invalid credentials',1);header('Location: /login');}}
(hl php)
// After action
$session->set_flash('info', 'Profile updated successfully', 1);
// Display next page
if (isset($session['info'])) {
echo $session['info'];
}
----
=== Performance Considerations ===
====Optimization Tips====
1. **Minimize Session Writes:**
- Session data only written during ##write_close()## or regeneration
- No unnecessary serialization during reads
2. **Garbage Collection:**
- Probabilistic GC (based on ##cf_gc_probability##)
- Only runs on ~2% of requests by default
- Customize based on your session volume
3. **Nonce Cleanup:**
- Expired nonces automatically removed on verification
- Verified nonces removed from storage
- No manual cleanup needed
4. **Session ID Validation:**
- Regex-based validation is fast
- No database lookup needed
5. **Caching Strategy:**
- Cache expensive lookups between session operations
- Session data loaded once per request
==== Benchmarks ====
Typical performance on modern hardware:
- Session start: ~1-5ms (file) / ~2-1ms (database)
- Session write: <1ms (file) / 1-5ms (database)
- Nonce generation: <1ms
- Nonce verification: <1ms
----
=== Troubleshooting ===
==== Session Not Starting ====
<!--markup:1:begin-->
php<!markup:1:end>
PHP
<!--markup:2:end-->if(!$session->start('myapp')){// Check reasons:// 1. Headers already sent?// 2. Storage backend not initialized?// 3. Permissions issue on session directory?debug_backtrace();}
Cookie Not Setting
<!markup:1:begin>
php<!--markup:1:end-->
<!--markup:2:begin-->
(hl php)
// If setcookie() returns false:
// - Check if headers_sent()
// - Check if cookie name is RFC 2616 compliant
// - Check if cookie value is properly encoded
==== Session ID Not Regenerating ====
<!--markup:1:begin-->
php<!markup:1:end>
PHP
<!--markup:2:end-->// If regenerate_id() returns false:// - Headers might be sent// - $active might be false// - Already regenerated once in this requestif(!$session->regenerate_id()){error_log("Regeneration failed: headers sent or session inactive");}
Nonce Verification Failing
PHP
// If verify_nonce() returns false:// 1. Nonce might be expired// 2. Nonce might be for different action// 3. Nonce might have been used already// 4. Session might have been reset// Debug:var_dump($session->__nonces);// See stored nonces
Session Data Lost
PHP
// Possible causes:// 1. write_close() not called (usually automatic via shutdown)// 2. Storage backend failing silently// 3. File permissions issues// 4. Session timeout due to cf_max_idle// 5. IP/UA/TLS validation failure (check message())if($message=$session->message()){error_log("Session issue: $message");}
TODO Items (From Code <!markup:1:begin>Comments)
<!markup:1:end> <!markup:2:begin>Comments)===
The following improvements are planned:
Do not store session ID in filename or DB index - store hash instead
Improves security by not exposing IDs in storage layer
Would require hashing logic in store_* methods
Log of IP changes and other possible security alerts
Track sticky__ip changes more comprehensively
Create security audit trail
Allocate internal unique session which lives through lifetime of uber-session
Multi-session management (parent/child sessions)
Useful for complex user flows
Do not delete old sessions, but use them as hijack pointers
Maintain session history for analysis
Detect potential session hijacking patterns
Implement session relationship tracking
All SIDs used later than 5secs of regenerations is hijacks
Detect and block delayed session ID usage
Current implementation allows 5-second window
Could be more granular
References
Security Standards
RFC 2616: HTTP/1.1 (Cookie syntax)
RFC 6265: HTTP State Management Mechanism
RFC 6234: US Secure Hash and Message Authentication Code Algorithms