Skip to content
← Back to Snippets
Code

Session Fingerprint Drift Review

Compares a stored session fingerprint with the current request fingerprint and returns an explicit drift decision.

Purpose

Compares a stored session fingerprint with the current request fingerprint and returns an explicit drift decision.

Snippet details

ContextSecurityLevelAdvancedCopy-and-paste statusMarked safe after review.

Categories

  • Security

Code

<?php

/*
 * Copyright (c) 2026 Jeffery L. Paris <jparis@phpog.com>.
 * Free for personal and internal use. Paid project use requires visible credit
 * to Jeffery L. Paris. Corporate use requires a paid license fee unless a
 * separate written license states otherwise.
 */

/**
 * Session Fingerprint Drift Review.
 *
 * Purpose:
 * Reviews whether stable request traits have changed enough to require a
 * session challenge, rotation, or logout.
 *
 * @param array $session_state Session state containing the stored fingerprint.
 * @param array $server_input $_SERVER-style request data.
 * @return array Drift review decision and current fingerprint.
 */
function ogSnippetSessionFingerprintDriftReview(array $session_state, array $server_input): array {
	$user_agent = '';
	$accept_language = '';
	$remote_address = '';

	if (isset($server_input['HTTP_USER_AGENT']) === true) {
		$user_agent = trim((string) $server_input['HTTP_USER_AGENT']);
	}

	if (isset($server_input['HTTP_ACCEPT_LANGUAGE']) === true) {
		$accept_language = strtolower(substr(trim((string) $server_input['HTTP_ACCEPT_LANGUAGE']), 0, 32));
	}

	if (isset($server_input['REMOTE_ADDR']) === true) {
		$remote_address = trim((string) $server_input['REMOTE_ADDR']);
	}

	$address_parts = explode('.', $remote_address);
	$address_group = '';

	if (count($address_parts) >= 2) {
		$address_group = $address_parts[0].'.'.$address_parts[1];
	}

	$fingerprint_seed = $user_agent.'|'.$accept_language.'|'.$address_group;
	$current_fingerprint = hash('sha256', $fingerprint_seed);
	$stored_fingerprint = '';

	if (isset($session_state['fingerprint']) === true) {
		$stored_fingerprint = (string) $session_state['fingerprint'];
	}

	if ($stored_fingerprint === '') {
		return array(
			'status' => 'initialize',
			'message' => 'No stored fingerprint exists for this session.',
			'fingerprint' => $current_fingerprint
		);
	}

	if (hash_equals($stored_fingerprint, $current_fingerprint) === true) {
		return array(
			'status' => 'stable',
			'message' => 'Session fingerprint has not drifted.',
			'fingerprint' => $current_fingerprint
		);
	}

	return array(
		'status' => 'drifted',
		'message' => 'Session fingerprint changed and should be challenged or rotated.',
		'fingerprint' => $current_fingerprint
	);
}

$expanse_server = array(
	'HTTP_USER_AGENT' => 'Rocinante Console',
	'HTTP_ACCEPT_LANGUAGE' => 'en-US,en;q=0.9',
	'REMOTE_ADDR' => '192.168.55.14'
);
$initial_review = ogSnippetSessionFingerprintDriftReview(array(), $expanse_server);
$second_review = ogSnippetSessionFingerprintDriftReview(array('fingerprint' => $initial_review['fingerprint']), $expanse_server);

echo 'Expanse session review: '.$second_review['status'];