Skip to content
← Back to Functions
Code

Signed Download Link Builder

Creates temporary signed download URLs for private files.

Function signature

ogCreateSignedDownloadLink(file = array(), options = array())

Categories

  • APIs and Webhooks

Parameters

fileFile/user data with file_id and user_id keys. Recognized keys: `file_id`, `user_id`.optionsSecret, base_url, and ttl_seconds options. Recognized keys: `base_url`, `secret`, `ttl_seconds`.

Return value

Short public-safe status message.

  • url
  • file_id
  • user_id
  • expires_at
  • nonce
  • signature

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, policy arrays, URLs, signatures, and caller-owned allowlists before use; keep secrets and internal paths 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.
 */

/**
 * Creates a temporary HMAC-signed download link.
 *
 * Authorization is still expected before calling this helper. This function
 * only builds tamper-resistant link data with an expiration timestamp.
 *
 * @param array $file File/user data with file_id and user_id keys.
 * @param array $options Secret, base_url, and ttl_seconds options.
 * @return array Signed download link data.
 */
function ogCreateSignedDownloadLink($file = array(), $options = array()) {
	$result = array(
		'success' => false,
		'message' => '',
		'data' => array()
	);

	if (!is_array($file)) {
		$result['message'] = 'File data must be an array.';
		return $result;
	}

	if (!is_array($options)) {
		$options = array();
	}

	$secret = '';
	if (!empty($options['secret'])) {
		$secret = (string)$options['secret'];
	}
	if (empty($secret)) {
		$result['message'] = 'Signing secret is required.';
		return $result;
	}

	$file_id = '';
	if (!empty($file['file_id'])) {
		$file_id = preg_replace('/[^a-zA-Z0-9_.-]/', '', (string)$file['file_id']);
	}
	$user_id = '';
	if (!empty($file['user_id'])) {
		$user_id = preg_replace('/[^a-zA-Z0-9_.-]/', '', (string)$file['user_id']);
	}
	if (empty($file_id) || empty($user_id)) {
		$result['message'] = 'File ID and user ID are required.';
		return $result;
	}

	$ttl_seconds = 900;
	if (!empty($options['ttl_seconds'])) {
		$ttl_seconds = (int)$options['ttl_seconds'];
	}
	if ($ttl_seconds < 60) {
		$ttl_seconds = 60;
	}

	$expires_at = time() + $ttl_seconds;
	$nonce = bin2hex(random_bytes(12));
	$base = $file_id . '|' . $user_id . '|' . $expires_at . '|' . $nonce;
	$signature = hash_hmac('sha256', $base, $secret);

	$base_url = '/download';
	if (!empty($options['base_url'])) {
		$base_url = trim((string)$options['base_url']);
	}

	$query = 'file_id=' . rawurlencode($file_id) . '&user_id=' . rawurlencode($user_id) . '&expires=' . $expires_at . '&nonce=' . rawurlencode($nonce) . '&sig=' . rawurlencode($signature);
	$separator = '?';
	if (strpos($base_url, '?') !== false) {
		$separator = '&';
	}
	$url = $base_url . $separator . $query;

	$result['success'] = true;
	$result['message'] = 'Signed download link created.';
	$result['data'] = array(
		'url' => $url,
		'file_id' => $file_id,
		'user_id' => $user_id,
		'expires_at' => $expires_at,
		'nonce' => $nonce,
		'signature' => $signature
	);

	return $result;
}