Laravel is one of the most popular PHP frameworks, but popularity attracts attention — including from attackers.
Even experienced developers can overlook security pitfalls that leave their applications vulnerable.
This guide covers six common Laravel security mistakes (based on the Laravel Security Guide video) and provides practical fixes with examples you can use today.
1. Invalidate Sessions After Password Changes
The mistake:
When a user changes their password, existing sessions on other devices often remain active. This means if an attacker already has access, they stay logged in even after a password update.
The fix:
Use Laravel’s logoutOtherDevices()
method and auth.session
middleware to invalidate all other active sessions.
Example:
// In your password update logic
use Illuminate\Support\Facades\Auth;
public function updatePassword(Request $request)
{
$request->validate([
'password' => 'required|confirmed|min:8'
]);
$user = Auth::user();
$user->password = bcrypt($request->password);
$user->save();
// Invalidate other sessions
Auth::logoutOtherDevices($request->password);
return back()->with('status', 'Password updated and other sessions logged out!');
}
Why it matters:
If an attacker had stolen a session token, they’ll be kicked out immediately after the user updates their password.
2. Secure File Downloads
The mistake:
Directly linking to file paths (e.g., /storage/invoices/invoice1.pdf
) allows unauthorized users to guess URLs and access private files.
The fix:
Use temporary signed URLs that expire after a set time, and validate signatures.
Example:
// Generating a signed URL
use Illuminate\Support\Facades\URL;
Route::get('/download/{file}', [DownloadController::class, 'download'])
->name('download')
->middleware('signed');
// In a controller
public function getSignedDownloadLink($file)
{
return URL::temporarySignedRoute(
'download',
now()->addMinutes(10),
['file' => $file]
);
}
// Validate in the route
public function download(Request $request, $file)
{
if (! $request->hasValidSignature()) {
abort(403, 'Invalid or expired link');
}
return response()->download(storage_path("app/private/{$file}"));
}
Why it matters:
Even if someone finds the link, it won’t work after the expiration time.
3. Scope Route Model Binding
The mistake:
Without scoping, a logged-in user can access another user’s resource just by guessing the ID.
The fix:
Use Laravel’s Scoped Bindings to ensure resources belong to the authenticated user.
Example:
// routes/web.php
Route::get('/users/{user}/posts/{post}', function (User $user, Post $post) {
return $post;
})->scopeBindings();
// This ensures $post belongs to $user
Why it matters:
This prevents horizontal privilege escalation, where users try to access others’ data.
4. Generate Unique Filenames
The mistake:
Saving files with their original names can lead to overwrites or information leaks.
The fix:
Generate unique names, for example using UUIDs.
Example:
use Illuminate\Support\Str;
public function upload(Request $request)
{
$file = $request->file('avatar');
$uniqueName = Str::uuid() . '.' . $file->getClientOriginalExtension();
$file->storeAs('avatars', $uniqueName, 'public');
return back()->with('status', 'File uploaded!');
}
Why it matters:
Even if two users upload profile.jpg
, they won’t overwrite each other’s files.
5. Encrypt IDs in URLs
The mistake:
Exposing raw IDs in URLs makes enumeration attacks easy — attackers can just increment numbers.
The fix:
Encrypt IDs before sending them in URLs, and decrypt when receiving.
Example:
use Illuminate\Support\Facades\Crypt;
// Generating an encrypted link
$id = 123;
$link = route('profile', ['id' => Crypt::encryptString($id)]);
// Receiving the encrypted ID
public function show($encryptedId)
{
$id = Crypt::decryptString($encryptedId);
$user = User::findOrFail($id);
return view('profile', compact('user'));
}
Why it matters:
Attackers can’t guess sequential IDs if they’re encrypted.
6. Encrypt Sensitive Database Fields
The mistake:
Storing sensitive data like API keys or personal identifiers in plaintext makes breaches devastating.
The fix:
Use Laravel’s Custom Casts to encrypt/decrypt automatically.
Example:
// In your model
public function casts(): array
{
return [
'api_key' => 'encrypted',
]
}
// Storing
$user->api_key = 'my-secret-key';
$user->save();
// Retrieving (auto-decrypted)
echo $user->api_key;
Why it matters:
Even if the database is compromised, encrypted values are unreadable without the key.
Final Thoughts
Security isn’t a “set it and forget it” task — it’s an ongoing process.
By proactively addressing these six vulnerabilities, you can significantly reduce your Laravel application’s attack surface.
Top comments (2)
Looks like Burts youtube video
Yeah, sure. As it's noted in the article: This guide covers six common Laravel security mistakes (based on the Laravel Security Guide video) and provides practical fixes with examples you can use today.