php 93 lines · 4 tabs

Serve Purchased Downloads Behind Signed Temporary URLs in Laravel

Shared by codesnips Aug 2026
4 tabs
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Facades\URL;

class DownloadableFile extends Model
{
    protected $fillable = [
        'product_id', 'disk', 'path', 'original_name', 'size_bytes', 'mime_type',
    ];

    protected $hidden = ['disk', 'path'];

    public function product(): BelongsTo
    {
        return $this->belongsTo(Product::class);
    }

    public function temporaryUrl(int $minutes = 10): string
    {
        return URL::temporarySignedRoute(
            'downloads.fetch',
            now()->addMinutes($minutes),
            ['file' => $this->getKey()]
        );
    }

    public function humanSize(): string
    {
        $mb = $this->size_bytes / 1048576;
        return number_format($mb, 2) . ' MB';
    }
}
4 files · php Explain with highlit

This snippet shows how a Laravel application ties downloadable files to a completed purchase and then serves them without ever exposing the underlying storage path. The core idea is that a raw storage location (an S3 key or a local disk path) should never appear in a link the browser can bookmark or share. Instead the app hands out a short-lived, cryptographically signed URL that proves both who may download and for how long.

In DownloadableFile model the pivot-like relationship is expressed: each row belongs to a product and carries the private disk and path plus display metadata. The accessor temporaryUrl() is the heart of the design — it calls URL::temporarySignedRoute, baking the file id and an expiry timestamp into a signature. Because the route is signed, tampering with the id or expiry invalidates the URL, so no server-side token table is required for expiry.

DownloadPolicy enforces ownership independently of the signature. A signed URL only guarantees the link is intact and unexpired; it does not prove the current user paid. The download method checks that the authenticated user has a completed order line containing the file's product, which closes the hole where a valid signed link could be forwarded to a non-buyer.

DownloadController wires it together. The signed middleware rejects malformed or expired URLs before any code runs, then authorize('download', $file) applies the policy. Only after both gates pass does it stream the bytes with Storage::disk(...)->download(...), which sets Content-Disposition and streams rather than loading the whole file into memory — important for large binaries. Using download on a private disk means the file itself is never publicly readable.

routes/web.php registers the named route downloads.fetch that temporaryUrl() references, guarded by auth and signed. The trade-off of signed routes over stored tokens is statelessness: nothing to clean up, but the expiry window is fixed at generation time and cannot be revoked early without rotating APP_KEY or adding a revocation check. For most purchase-download flows the combination of a signed URL plus a policy is the right balance of security and simplicity.


Related snips

Share this code

Here's the card — post it anywhere.

Serve Purchased Downloads Behind Signed Temporary URLs in Laravel — share card
Link copied