Skip to content
← Back to Functions
Code

CSRF Token Validator

Validates a submitted CSRF token against the stored session token and returns a clear pass/fail result.

Function signature

ogValidateCsrfToken(submitted_token, stored_token_record, scope)

Categories

  • Security

Parameters

submitted_tokenToken received from a POST form.stored_token_recordStored token metadata. Recognized keys: `expires_at`, `scope`, `token`, `token_hash`.scopeExpected scope name.

Return value

Short public-safe status message.

  • valid
  • scope
  • should_rotate

Compatibility

Existing function name and call order preserved; metadata signature corrected to source.

Minimum PHP version: 7.4

Security notes

Validate request method, identity, permissions, and caller-owned allowlists before use; keep secrets out of public output.

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.
 */

/**
 * Validates a submitted CSRF token against a stored scoped token record.
 *
 * The stored record may contain either the raw token or a SHA-256 token_hash.
 * Hash storage is preferred so accidental session dumps do not expose the raw
 * browser token.
 *
 * @param string $submitted_token Token received from a POST form.
 * @param array $stored_token_record Stored token metadata.
 * @param string $scope Expected scope name.
 * @return array Structured validation result.
 */
function ogValidateCsrfToken($submitted_token, $stored_token_record, $scope) {
	$result = array(
		'success' => false,
		'message' => '',
		'data' => array('valid' => false)
	);

	$submitted_token = trim((string)$submitted_token);
	$scope = trim((string)$scope);

	if (empty($submitted_token)) {
		$result['message'] = 'Missing submitted CSRF token.';
		return $result;
	}

	if (!is_array($stored_token_record)) {
		$result['message'] = 'Stored CSRF token record is invalid.';
		return $result;
	}

	if (empty($scope)) {
		$result['message'] = 'Missing CSRF scope.';
		return $result;
	}

	if (empty($stored_token_record['scope']) || !hash_equals((string)$stored_token_record['scope'], $scope)) {
		$result['message'] = 'CSRF scope mismatch.';
		return $result;
	}

	if (empty($stored_token_record['expires_at'])) {
		$result['message'] = 'CSRF token expiration is missing.';
		return $result;
	}

	if ((int)$stored_token_record['expires_at'] < time()) {
		$result['message'] = 'CSRF token expired.';
		return $result;
	}

	$submitted_hash = hash('sha256', $submitted_token);
	$valid = false;

	if (!empty($stored_token_record['token_hash'])) {
		if (hash_equals((string)$stored_token_record['token_hash'], $submitted_hash)) {
			$valid = true;
		}
	} elseif (!empty($stored_token_record['token'])) {
		if (hash_equals((string)$stored_token_record['token'], $submitted_token)) {
			$valid = true;
		}
	}

	if (!$valid) {
		$result['message'] = 'CSRF token mismatch.';
		return $result;
	}

	$result['success'] = true;
	$result['message'] = 'CSRF token validated.';
	$result['data'] = array(
		'valid' => true,
		'scope' => $scope,
		'should_rotate' => true
	);

	return $result;
}