Skip to content
← Back to Snippets
Code

Admin CSRF Bulk Action Gate

Validates a POSTed CSRF token and a bulk-action selection before an admin controller performs a state-changing operation.

Purpose

Validates a POSTed CSRF token and a bulk-action selection before an admin controller performs a state-changing operation.

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

/**
 * Admin CSRF Bulk Action Gate.
 *
 * Purpose:
 * Validates the CSRF token, action name, and selected IDs before a bulk admin
 * operation is allowed to continue.
 *
 * @param array $post_input Sanitized or raw $_POST-style input array.
 * @param array $session_state Session array containing the stored CSRF token.
 * @param array $allowed_actions Approved bulk action names.
 * @param int $max_items Maximum IDs allowed in one request.
 * @return array Gate result with action, IDs, and errors.
 */
function ogSnippetAdminCsrfBulkActionGate(array $post_input, array $session_state, array $allowed_actions, int $max_items): array {
	$errors = array();
	$approved_ids = array();
	$selected_action = '';

	if ($max_items < 1) {
		$max_items = 1;
	}

	$posted_token = '';
	$stored_token = '';

	if (isset($post_input['csrf_token']) === true) {
		$posted_token = (string) $post_input['csrf_token'];
	}

	if (isset($session_state['csrf_token']) === true) {
		$stored_token = (string) $session_state['csrf_token'];
	}

	if ($posted_token === '' || $stored_token === '') {
		$errors[] = 'Missing CSRF token.';
	} elseif (hash_equals($stored_token, $posted_token) === false) {
		$errors[] = 'CSRF token mismatch.';
	}

	if (isset($post_input['bulk_action']) === true) {
		$selected_action = trim((string) $post_input['bulk_action']);
	}

	if ($selected_action === '') {
		$errors[] = 'Bulk action is required.';
	} elseif (in_array($selected_action, $allowed_actions, true) === false) {
		$errors[] = 'Bulk action is not allowed.';
	}

	if (isset($post_input['selected_ids']) === false || is_array($post_input['selected_ids']) === false) {
		$errors[] = 'No item IDs were selected.';
	} else {
		foreach ($post_input['selected_ids'] as $raw_id) {
			$id = filter_var($raw_id, FILTER_VALIDATE_INT);

			if ($id !== false && $id > 0) {
				$approved_ids[(int) $id] = (int) $id;
			}
		}
	}

	$approved_ids = array_values($approved_ids);

	if (count($approved_ids) < 1) {
		$errors[] = 'No valid item IDs were selected.';
	}

	if (count($approved_ids) > $max_items) {
		$errors[] = 'Too many item IDs were submitted in one request.';
	}

	return array(
		'allowed' => count($errors) === 0,
		'action' => $selected_action,
		'ids' => $approved_ids,
		'errors' => $errors
	);
}

$admin_post = array(
	'csrf_token' => 'stargate-alpha-token',
	'bulk_action' => 'archive',
	'selected_ids' => array('101', '102', 'bad-id', '103')
);
$admin_session = array('csrf_token' => 'stargate-alpha-token');
$gate_result = ogSnippetAdminCsrfBulkActionGate($admin_post, $admin_session, array('archive', 'publish'), 25);

if ($gate_result['allowed'] === true) {
	echo 'Stargate admin gate approved '.count($gate_result['ids']).' IDs.';
} else {
	echo 'Stargate admin gate blocked the request.';
}