php
41 lines · 6 steps
Time-limited signed download links in Laravel
A controller that hands out expiring, tamper-proof URLs and streams the file only when the signature checks out.
Explained by
highlit
1<?php
2
3namespace App\Http\Controllers;
4
5use App\Models\Document;
6use Illuminate\Http\Request;
7use Illuminate\Support\Facades\Storage;
8use Illuminate\Support\Facades\URL;
9use Symfony\Component\HttpFoundation\StreamedResponse;
10
11class DocumentDownloadController extends Controller
12{
13 public function generate(Request $request, Document $document)
14 {
15 $this->authorize('download', $document);
16
17 $url = URL::temporarySignedRoute(
18 'documents.download',
19 now()->addMinutes(15),
20 ['document' => $document->id]
21 );
22
23 return response()->json([
24 'download_url' => $url,
25 'expires_at' => now()->addMinutes(15)->toIso8601String(),
26 ]);
27 }
28
29 public function download(Request $request, Document $document): StreamedResponse
30 {
31 abort_unless($request->hasValidSignature(), 403, 'This download link has expired.');
32
33 $disk = Storage::disk($document->disk);
34
35 abort_unless($disk->exists($document->path), 404);
36
37 return $disk->download($document->path, $document->original_name, [
38 'Content-Type' => $document->mime_type,
39 ]);
40 }
41}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Signed URLs let you delegate access without exposing storage paths or requiring a session on the download itself.
- 2Splitting link generation from the actual download keeps authorization and delivery as separate, verifiable steps.
- 3Streaming through the Storage facade avoids loading the whole file into memory and works across local and cloud disks.
Related explainers
php
<?php namespace App\Database;
Building a fluent SQL query builder in PHP
fluent-interface
method-chaining
sql
Intermediate
10 steps
php
<?php namespace App\Http\Requests;
Normalizing input in a Laravel FormRequest
validation
authorization
input-normalization
Intermediate
6 steps
php
<?php namespace App\Http;
Building an onion middleware pipeline in PHP
middleware
closures
array-reduce
Advanced
9 steps
php
<?php namespace Database\Seeders;
Building blog test data with Laravel factories
seeding
factories
relationships
Intermediate
9 steps
php
<?php namespace App\Notifications;
How a queued weekly digest email works in Laravel
notifications
queues
email
Intermediate
7 steps
php
public function importCustomers(UploadedFile $file): array { $errors = []; $imported = 0;
Validating a CSV import in Laravel
csv-parsing
validation
error-accumulation
Intermediate
8 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/time-limited-signed-download-links-in-laravel-explained-php-74e6/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.