php
45 lines · 7 steps
Building signed, expiring download URLs in PHP in Laravel
An HMAC-signed URL scheme that lets you hand out time-limited download links no one can tamper with.
Explained by
highlit
1<?php
2
3namespace App\Services;
4
5use Illuminate\Support\Carbon;
6
7class SignedDownloadUrl
8{
9 private string $key;
10
11 public function __construct(string $key)
12 {
13 $this->key = $key;
14 }
15
16 public function make(string $path, int $ttlSeconds = 300): string
17 {
18 $expires = Carbon::now()->addSeconds($ttlSeconds)->getTimestamp();
19
20 $payload = [
21 'path' => $path,
22 'expires' => $expires,
23 ];
24
25 $payload['signature'] = $this->sign($path, $expires);
26
27 return '/download?' . http_build_query($payload);
28 }
29
30 public function verify(string $path, int $expires, string $signature): bool
31 {
32 if (Carbon::now()->getTimestamp() > $expires) {
33 return false;
34 }
35
36 return hash_equals($this->sign($path, $expires), $signature);
37 }
38
39 private function sign(string $path, int $expires): string
40 {
41 $data = $path . '|' . $expires;
42
43 return hash_hmac('sha256', $data, $this->key);
44 }
45}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Signing the path and expiry together with a secret key makes a URL impossible to forge or extend without that key.
- 2Embedding an expiry timestamp in the signed payload turns a link into a self-limiting credential.
- 3Comparing signatures with a constant-time function like hash_equals defends against timing attacks.
Related explainers
php
<?php declare(strict_types=1);
A single-action JSON user controller in PHP
invokable-class
json-api
validation
Intermediate
7 steps
php
<?php namespace App\Services\Provisioning;
Sequencing site setup with Laravel job chains
job chains
queues
async workflows
Intermediate
6 steps
php
final class UserActivityStream { public function __construct( private readonly \PDO $db,
Streaming user activity with PHP generators
generators
pagination
lazy-evaluation
Intermediate
8 steps
php
<?php namespace App\Casts;
How a custom Eloquent cast wraps an Address in Laravel
value-objects
serialization
data-mapping
Intermediate
7 steps
php
use Illuminate\Support\Facades\Route; use App\Http\Controllers\Admin\DashboardController; use App\Http\Controllers\Admin\UserController; use App\Http\Controllers\Admin\ReportController;
How nested route groups compose in Laravel
routing
middleware
authorization
Intermediate
8 steps
go
package middleware import ( "bytes"
Verifying webhook HMAC signatures in Gin
hmac
middleware
webhooks
Intermediate
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/building-signed-expiring-download-urls-in-php-in-laravel-explained-php-2ed2/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.