AlgoInsight

Q&A

How to allow only authenticated users access to files in Laravel

By AlgoInsight Team2026-06-18laravelphp

Overview

Files placed in public/ are accessible to anyone with the URL - there's no auth check. To restrict access, files must live outside the web root and be delivered through an authenticated Laravel route that confirms the user has permission before streaming the file. The framework middleware handles the auth gate; the controller handles the file delivery.

graph TD
    A[User requests file URL] --> B[Laravel route hit]
    B --> C[auth middleware: authenticated?]
    C -->|no| D[Redirect to login / 401]
    C -->|yes| E[Controller checks permission]
    E --> F["Storage::download() streams file"]
    F --> G[File bytes sent to user]
    style C fill:#1e1b4b,color:#fff

Step 1: Store Files Outside the Public Root

Use the local (private) disk which defaults to storage/app:

use Illuminate\Support\Facades\Storage;

// When uploading:
$path = $request->file('document')->store('documents', 'local');
// Stored at storage/app/documents/filename.pdf
// NOT accessible via URL - no web server path to it

storage/app/ is outside public/, so there's no direct URL.

Step 2: Define a Protected Route

Wrap the download route in the auth middleware:

// routes/web.php
use App\Http\Controllers\FileController;
use Illuminate\Support\Facades\Route;

Route::middleware(['auth'])->group(function () {
    Route::get('/files/{filename}', [FileController::class, 'download'])
         ->name('file.download');
});

Any unauthenticated request to this route is redirected to the login page automatically.

Step 3: Stream the File in the Controller

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;

class FileController extends Controller
{
    public function download(Request $request, string $filename)
    {
        // Build the storage path
        $path = 'documents/' . $filename;

        // Confirm the file exists
        if (!Storage::disk('local')->exists($path)) {
            abort(404);
        }

        // Return the file as a download
        return Storage::disk('local')->download($path);
        // Or stream inline (e.g. PDF preview):
        // return response()->file(storage_path('app/' . $path));
    }
}

Step 4: Per-User Authorization (Gates / Policies)

For ownership or role checks beyond "logged in":

// In a FilePolicy:
public function view(User $user, File $file): bool
{
    return $user->id === $file->user_id;
}

// In the controller:
$this->authorize('view', $file);   // aborts with 403 if denied

Use authorize() (or Gate::authorize) for fine-grained checks beyond the auth middleware.

Step 5: Temporary Signed URLs (for expiring access)

For time-limited, shareable file links without storing auth state:

$url = URL::temporarySignedRoute(
    'file.download',
    now()->addMinutes(30),
    ['filename' => $filename]
);

The signed URL includes a hash that Laravel verifies; it expires after the given time. Add ->middleware('signed') to the route to validate it.

Summary of the Pattern

  1. Store files on the private disk (not public/).
  2. Serve through a route protected by the auth middleware.
  3. In the controller, apply any per-resource authorization (policy/gate).
  4. Use Storage::download() to stream the file - never expose the real path.

Why This Matters in Interviews

Laravel security interviews use this to test understanding of how files and authentication intersect. Key points: files in public/ are unprotected by definition - always store sensitive files on a private disk; the auth middleware is the gate; deliver with Storage::download(); and use Policies for per-user ownership checks. Temporary signed URLs for expiring access is a strong additional point.

Common Pitfalls

References

More answers