-
Notifications
You must be signed in to change notification settings - Fork 2
Description
Hi @ryancramerdesign - I feel like this could be improved.
* - 0 or false: Fingerprint off
* - 1 or true: Fingerprint on with system default settings
* (Useragent for installations after Oct 31 2025 at 5pm)
* (Useragent + Remote IP for existing installations)
*
* Custom settings:
*
* - 2: Remote IP
* - 4: Forwarded/client IP (can be spoofed)
* - 8: Useragent
* - 16: Accept header
*
* To use the custom settings above, select one or more of those you want
* to fingerprint, note the numbers, and use them like in the examples:
* ~~~~~~
* // to fingerprint just remote IP
* $config->sessionFingerprint = 2;
*
* // to fingerprint remote IP and useragent:
* $config->sessionFingerprint = 2 | 8;
*
* // to fingerprint remote IP, useragent and accept header:
* $config->sessionFingerprint = 2 | 8 | 16;
* ~~~~~~
*
* If using fingerprint in an environment where the user’s IP address may
* change during the session, you should fingerprint only the useragent
* and/or accept header, or disable fingerprinting.
*
* If using fingerprint with an AWS load balancer, you should use one of
* the options that uses the “client IP” rather than the “remote IP”,
* fingerprint only useragent and/or accept header, or disable fingerprinting.
I am using Cloudfront rather than ALB, but I was still having issues getting logged out with certain AJAX calls in the admin.
In my config.php file I have been using:
// set fingerprinting to useragent only (8) if outside of admin. Otherwise this defaults to max restrictions via "1"
if(!str_contains($_SERVER['REQUEST_URI'], 'admin')) {
$config->sessionFingerprint = 8;
}
but the fallback to 1 for the admin was the problem. This is on an old installation (before the Oct 2025 change date). The AJAX for PageList and things like loading fields and templates was fine, but I have some custom Process modules with AJAX calls and they were logging me out.
Anyway, I removed the if(!str_contains($_SERVER['REQUEST_URI'], 'admin')) { condition so now the admin is forced to 8 as well, but I am curious what you learned about using $config->sessionFingerprint = 4 | 8; because I believe that is what you are suggesting for ALB so I am wondering why you didn't use that as the new default? Maybe because the Forwarded/client IP can be spoofed, it's not really worthwhile at all?
I suppose if nothing else I am suggesting that the docs above should mention things like Cloudfront as well and why we might consider adding 4 in addition to 8.
Here are some thoughts (this time from Gemini - it does a great job of deep research) - it's interesting to me that it's suggesting 24 (8 | 16)
Gemini response
Even if a developer uses a "relaxed" fingerprint like bitmask 8 or 24, the sessionChallenge ensures that a simple theft of the session cookie is insufficient for hijacking. An attacker would need to steal both cookies and replicate the browser's header environment. This multi-factor cookie approach provides "strictness" that is independent of network volatility, making it the preferred method for securing sessions on modern, mobile-heavy sites.
Advanced Rigor: Partial IP Matching and Contextual Hooks
If the transition to bitmask 24 (User-Agent + Accept) is still deemed insufficient, the ProcessWire API allows for the implementation of custom validation logic that is "smarter" than the core's all-or-nothing IP matching. The Session::isValidSession method is hookable, allowing developers to inject their own rules for what constitutes a valid session.
Partial IP Masking via Hooks
A common high-security strategy is to validate only the first two or three octets of an IPv4 address (e.g., 192.168.x.x). This provides a "geographic lock" or "ISP lock" while allowing for the minor IP rotations that occur within a specific provider's network.
By implementing an after hook on Session::isValidSession, a developer can allow a session to continue if the current IP address matches the first 16 or 24 bits of the original IP address. This approach bridges the gap between bitmask 8 and bitmask 10, providing much of the security of the latter with the stability of the former.
/**
* Conceptual Hook Implementation for Partial IP Validation
*/
$wire->addHookAfter('Session::isValidSession', function($event) {
// If the core has already marked it invalid, we respect that.
if(!$event->return) return;
$session = $event->object;
$currentIP = $session->getIP();
$storedIP = $session->get('login_ip'); // Stored at login success
if($currentIP && $storedIP) {
// Compare first 3 octets (e.g., 123.123.123.xxx)
$currentParts = explode('.', $currentIP);
$storedParts = explode('.', $storedIP);
if(count($currentParts) === 4 && count($storedParts) === 4) {
if($currentParts!== $storedParts |
| $currentParts!== $storedParts) {
// IPs are from completely different networks; invalidate.
$event->return = false;
}
}
}
});
This third-order insight suggests that the "strictness" requested by the user does not have to be limited to the pre-defined bitmask integers. The flexibility of the ProcessWire hook system allows for a "fluid strictness" that adapts to the specific risk profile of the user base.
Impact on Form Integrity and CSRF Protection
The choice of session strictness has profound implications for user experience beyond the login screen, specifically regarding long-form data entry and Cross-Site Request Forgery (CSRF) protection. ProcessWire’s CSRF tokens are cryptographically tied to the active session. If a user’s session is terminated due to a fingerprint mismatch (e.g., their IP changed while they were typing a long blog post), the CSRF token associated with the form will also be invalidated.
When the user attempts to save their work, the isValidSession() check fails, the user is redirected to the login page, and the POST data is frequently lost. This is a common source of frustration for administrators and content editors. Ryancramer notes that for guest users, the session fingerprint is typically not used, meaning guest form submissions are less likely to fail due to IP rotation. However, for authenticated staff, a strictness level of 10 or higher is a significant risk to productivity. Moving to bitmask 8 or 24 preserves the session—and therefore the CSRF token—throughout these network transitions, ensuring that form submissions remain successful.
The 2026 Paradigm: ProcessWire 3.0.255 Features
The release of ProcessWire 3.0.255 in early 2026 brought several relevant updates to the session and security infrastructure. Among the "best new versions yet," 3.0.255 introduced more flexible Markup Regions and conditional return value hooks, but specifically noted "Session fingerprint change" as a core improvement. This update refined how the core handles the transition between different validation states, providing better logging when a fingerprint mismatch occurs and allowing developers to more easily identify why a user was logged out.
Furthermore, the 2026 installer improvements and PHP 8.4/8.5 compatibility updates have hardened the way session cookies are handled at the server level, ensuring that the latest browser security standards (such as improved SameSite enforcement) are utilized by default.
Security Comparison of Aggregated Bitmask Values
To provide a clear technical roadmap for increasing strictness, the following table compares various bitmask combinations based on their relative security and stability.
Bitmask Sum Formula Strictness Level Stability Level Hijacking Difficulty Recommended Use Case
8 8 Baseline High Low Public sites; mobile-heavy usage.
24 8 + 16 Moderate High Moderate
Balanced corporate/public portals.
10 8 + 2 High Low High Static office IPs; high-security admin.
26 8 + 16 + 2 Extreme Very Low Extreme
Financial/Critical systems; static IPs.
12 8 + 4 Moderate Moderate Moderate Sites behind trusted proxies.
28 8 + 16 + 4 High Moderate High High-security sites behind trusted proxies.
The move from 8 to 24 is the only "slightly stricter" step that does not involve the volatility of the IP address. Every other step (adding 2 or 4) introduces a dependency on the network layer that has already caused issues for the user.
Conclusion and Strategic Recommendation
The architectural review of ProcessWire’s session validation system indicates that the user’s successful transition to bitmask 8 was a necessary response to the instability of modern IP routing. To satisfy the requirement for a "slightly stricter" configuration, the analysis favors the integration of the Accept header (bitmask 16), resulting in a total bitmask value of 24.
This configuration—$config->sessionFingerprint = 24;—represents the optimal balance for the following reasons:
Stability: It relies on browser-intrinsic headers which do not change when a user moves between networks, thus preventing the "constant logout" issue.
Uniqueness: It differentiates between browsers and devices with more precision than the User-Agent alone, providing a more unique fingerprint.
Consistency: It maintains the integrity of CSRF tokens during long-form data entry, protecting against data loss for content editors.
Complementary Security: It works in tandem with the sessionChallenge mechanism, which remains the primary line of defense against session hijacking regardless of the fingerprint setting.
For environments demanding even higher rigor without the fragility of full IP tracking, the developer should consider implementing a custom hook on Session::isValidSession to perform partial IP matching. This provides a geographic anchor for the session that is significantly more robust than any static bitmask value. Ultimately, the transition from 8 to 24 provides a measurable increase in security posture while preserving the operational "fix" achieved by the user's initial configuration change. In the context of ProcessWire 3.0.255, this strategy aligns with the core's movement toward stable, header-based validation for the modern web.