Skip to content
← Back to Functions
Code

One Time Code Validator

Validates one-time codes with attempt limits, expiry checks, and replay prevention.

Function signature

ogValidateOneTimeCode(submitted_code, stored_hash, expires_at, attempt_count = 0, max_attempts = 5)

Categories

  • Security

Parameters

submitted_codeSubmitted one-time code.stored_hashStored password_hash() value for the code.expires_atExpiration timestamp.attempt_countCurrent failed attempt count.max_attemptsMaximum permitted attempts.

Return value

Short public-safe status message.

  • valid
  • invalidate
  • attempt_count

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 one-time codes with attempt limits, expiry checks, and replay prevention.
 *
 * Primary use case: Verify emailed codes or sensitive admin actions.
 * Typical inputs: submitted code, stored hash, attempt count.
 * Typical output: validation result.
 *
 * Implementation note: Use constant-time verification behavior and invalidate after success.
 *
 * @param string $submitted_code Submitted one-time code.
 * @param string $stored_hash Stored password_hash() value for the code.
 * @param int $expires_at Expiration timestamp.
 * @param int $attempt_count Current failed attempt count.
 * @param int $max_attempts Maximum permitted attempts.
 * @return array Structured result data with success, message, and data keys.
 */
function ogValidateOneTimeCode($submitted_code, $stored_hash, $expires_at, $attempt_count = 0, $max_attempts = 5) {
	$result = array(
		'success' => false,
		'message' => '',
		'data' => array()
	);

	$submitted_code = strtoupper(trim((string)$submitted_code));
	$stored_hash = (string)$stored_hash;
	$expires_at = (int)$expires_at;
	$attempt_count = (int)$attempt_count;
	$max_attempts = (int)$max_attempts;

	if ($max_attempts < 1) {
		$max_attempts = 1;
	}
	if (empty($submitted_code) || empty($stored_hash)) {
		$result['message'] = 'Verification code data is incomplete.';
		return $result;
	}
	if ($attempt_count >= $max_attempts) {
		$result['message'] = 'Too many verification attempts.';
		$result['data'] = array('valid' => false, 'invalidate' => true);
		return $result;
	}
	if ($expires_at < time()) {
		$result['message'] = 'Verification code expired.';
		$result['data'] = array('valid' => false, 'invalidate' => true);
		return $result;
	}
	if (!password_verify($submitted_code, $stored_hash)) {
		$result['message'] = 'Verification code mismatch.';
		$result['data'] = array('valid' => false, 'invalidate' => false, 'attempt_count' => $attempt_count + 1);
		return $result;
	}

	$result['success'] = true;
	$result['message'] = 'Verification code accepted.';
	$result['data'] = array('valid' => true, 'invalidate' => true);

	return $result;
}