Laravel cookbook · Intermediate

Validate uploads and serve private files with temporary URLs

Uploads land on local disks, accept any file type, and private files are publicly accessible.

Create a free account to save this page, take notes and track your progress. Sign in

Approach

Validate type and size, store on a private disk (S3) under a generated name, save the path, and give users short-lived signed URLs after an authorization check.

Solution

<?php
namespace App\Http\Controllers;

use App\Models\Document;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\Rules\File;

class DocumentController extends Controller
{
    public function store(Request $request)
    {
        $request->validate([
            'file' => ['required', File::types(['pdf', 'png', 'jpg'])->max(10 * 1024)],   // KB
        ]);

        $path = $request->file('file')->store("documents/{$request->user()->id}", 's3');

        $document = $request->user()->documents()->create([
            'path' => $path,
            'original_name' => $request->file('file')->getClientOriginalName(),
        ]);

        return response()->json(['id' => $document->id], 201);
    }

    public function download(Document $document)
    {
        Gate::authorize('view', $document);

        return redirect(Storage::disk('s3')->temporaryUrl($document->path, now()->addMinutes(5), [
            'ResponseContentDisposition' => 'attachment; filename="'.addslashes($document->original_name).'"',
        ]));
    }
}

Pitfalls

  • Storing uploads under public/ where anyone can guess URLs.
  • Trusting the client file name for the stored path.
  • Large uploads through PHP when browsers could upload directly to S3 with presigned URLs.

Variations

  • Use Storage::disk('s3')->temporaryUploadUrl() for direct browser uploads.
  • Process images (resize, strip EXIF) in a queued job after upload.

Further reading: Temporary URLs

Reference: https://laravel.com/docs/13.x/filesystem#temporary-urls

More Laravel recipes

Was this page helpful?