Bezpečnost Laravel aplikace v praxi

Laravel dává do ruky slušnou výbavu proti útokům, ale jen když ji používáte správně. Procházím prakticky ochranu proti CSRF, hesla, autorizaci i konfiguraci produkce.
Publikováno 07.09.2026

Bezpečnost Laravel aplikace v praxi

Publikováno

· Autor: · Pod kapotou · Bezpečnost · 7 min čtení

Obsah článku

Laravel má bezpečnostní výbavu na slušné úrovni už z krabice, ale sám o sobě vás neochrání. Viděl jsem dost aplikací, kde vývojář ochranu nechtěně vypnul nebo obešel ve snaze si něco zjednodušit — a přesně tam pak vznikla díra. Bezpečnost totiž není jedna funkce, kterou zapnete, ale přístup, který se táhne celým projektem od validace vstupu přes autorizaci až po nasazení na produkci. Projdu prakticky, na co si dávám pozor v každém projektu, a ukážu to na kódu.

Ochrana proti CSRF a XSS

Dvě nejběžnější zranitelnosti webových aplikací. Laravel proti nim chrání ve výchozím stavu docela dobře, ale jen když jeho ochranu nesabotujete. U formulářů nikdy nevypínejte CSRF token a v Blade nechte výstup escapovat — obojí je zdarma a chrání vás automaticky.

<form method="POST" action="/objednavka">
    @csrf
    <input type="text" name="jmeno">
</form>

{{-- Bezpečné: Blade automaticky escapuje --}}
<p>{{ $uzivatel->jmeno }}</p>

{{-- Nebezpečné: syrový výstup jen u dat, kterým věříte --}}
<p>{!! $duveryhodneHtml !!}</p>

Klíčové je nepodlehnout pokušení vypnout CSRF ochranu jen proto, že „to nefunguje". Devětkrát z deseti to znamená, že ve formuláři nebo v hlavičce požadavku prostě chybí token — ne že by ochrana byla na obtíž. U jednorázových API endpointů volaných z jiné služby použijte raději cílené vyjmutí cesty a token nahraďte podepsaným tokenem nebo Sanctum, místo abyste ochranu vypnuli plošně. Detailněji se oběma útokům věnuji v článku SQL injection a XSS: jak se bránit.

Hesla, autentizace a autorizace

Hesla nikdy neukládám v čitelné podobě. Laravel je hashuje sám, stačí na modelu použít cast hashed nebo zavolat Hash::make(). Zásadní je nepřehazovat si dva rozdílné pojmy: autentizace řeší kdo uživatel je, autorizace co smí. Přihlášený uživatel neznamená, že smí editovat cizí objednávku.

use Illuminate\Support\Facades\Hash;

$user->password = Hash::make($request->password);

// Autorizace přes policy: kontroluj oprávnění, ne jen přihlášení
public function update(User $user, Post $post): bool
{
    return $user->id === $post->user_id;
}

Nejčastější chyba, kterou v tomhle vídám, je kontrola typu „je uživatel přihlášený?" tam, kde má být „patří tenhle záznam jemu?". Útočník se běžně přihlásí pod svým účtem a pak jen v adrese přepíše cizí ID. Bez kontroly vlastnictví mu aplikace ochotně vrátí cizí data.

  • Přihlášení zvládá Laravel bezpečně, autorizaci řeším přes policy a gaty, ne ručními if v kontroleru.
  • Validaci vstupů vždy přes Form Request — víc v článku o validaci přes Form Request.
  • Citlivé akce jako přihlášení nebo reset hesla chráním rate limiterem proti hádání hesel hrubou silou.

Databáze a práce se vstupem

K databázi přistupuji zásadně přes Eloquent nebo query builder, které parametrizují dotazy za mě a oddělí data od příkazu. Ruční skládání SQL z uživatelského vstupu je nejrychlejší cesta k průšvihu, protože útočníkovi otevře dveře přímo do dat. Když už syrový dotaz opravdu potřebuji, hodnoty vždycky předávám jako vázané parametry, nikdy je nevpisuji do řetězce.

// ŠPATNĚ: vstup jde přímo do dotazu
$users = DB::select("SELECT * FROM users WHERE email = '{$request->email}'");

// SPRÁVNĚ: hodnota jako vázaný parametr
$users = DB::select('SELECT * FROM users WHERE email = ?', [$request->email]);

// Nejlépe rovnou přes Eloquent, který parametrizuje za vás
$user = User::query()->where('email', $request->email)->first();

Jedna pastička k tomu: vázané parametry chrání hodnoty, ale ne názvy sloupců nebo směr řazení. Pokud tyhle části dotazu skládáte podle toho, co přijde z URL, ověřte je proti pevnému seznamu povolených hodnot — jinak si zaděláváte na stejný problém jinými dveřmi.

  • Nikdy neposílám nezpracovaný vstup do syrového SQL.
  • Hromadné přiřazení hlídám přes $fillable, ať útočník nepodstrčí pole, které měnit nemá (třeba is_admin).
  • Řazení a filtry z URL mapuji na whitelist, ne přímo do dotazu.

Konfigurace a tajemství

Spousta úniků nevznikne chybou v kódu, ale tím, že se do repozitáře omylem dostane přístupový klíč nebo se na produkci nechá zapnutý ladicí režim. Citlivé údaje proto drží konfigurace mimo Git, v souboru .env, který do verzování nikdy nepatří.

  • Soubor .env mám v .gitignore, do repozitáře commituji jen .env.example bez hodnot.
  • Na produkci vypínám ladicí režim — jinak první chybová hláška vysype návštěvníkovi útržky kódu, cesty a klíče.
  • Když klíč přece jen unikne do historie Gitu, neřeším to jen smazáním — rovnou ho zneplatním a vygeneruji nový.

Sessions, cookies a přihlášení

I dobře zahašované heslo je k ničemu, když někdo ukradne přihlášenou session. Na tohle vývojáři často zapomínají, protože session „prostě funguje". Laravel nabízí rozumné výchozí hodnoty, ale pár věcí stojí za kontrolu v konfiguraci.

// config/session.php
'secure' => env('SESSION_SECURE_COOKIE', true), // cookie jen přes HTTPS
'http_only' => true,   // cookie nepřístupná z JavaScriptu (obrana proti XSS)
'same_site' => 'lax',  // omezí odesílání cookie na cizí weby (obrana proti CSRF)

Ke třem věcem se snažím vést každý projekt: cookie posílat výhradně přes HTTPS, znepřístupnit ji JavaScriptu a při přihlášení nechat regenerovat identifikátor session. Poslední bod brání útoku, kdy útočník podstrčí oběti známé ID session ještě před přihlášením a po přihlášení ho zneužije. Vypadá to jako drobnost, ale právě na těchhle detailech stojí rozdíl mezi aplikací, která krádež session přežije, a tou, kde stačí jeden ukradený cookie k převzetí účtu.

  • Při přihlášení volám $request->session()->regenerate(), ať se ID změní.
  • Při odhlášení session zneplatňuji celou, ne jen mažu jednu položku.
  • U citlivých akcí vyžaduji nedávné potvrzení hesla přes middleware password.confirm.

Nahrávání souborů

Formulář pro nahrání souboru je klasické místo, kde se dá aplikace položit. Když důvěřuji tomu, co uživatel pošle, může místo obrázku nahrát skript a pak si ho přes prohlížeč spustit. Proto typ souboru ověřuji na serveru — přípona v názvu se dá přepsat za pár vteřin a nic neznamená.

$request->validate([
    // Kontroluj skutečný MIME typ, ne jen příponu v názvu
    'avatar' => ['required', 'image', 'mimes:jpg,png,webp', 'max:2048'],
]);

// Ukládej mimo veřejnou složku a nech Laravel vygenerovat bezpečný název
$path = $request->file('avatar')->store('avatary');
  • Soubory ukládám mimo veřejný public/ a servíruji je přes kontroler s kontrolou oprávnění.
  • Název souboru nechávám generovat framework, ať do cesty nikdo nepropašuje ../.
  • Velikost i typ omezuji už ve validaci, ne až někde v kódu za tím.

Produkce a provoz

Bezpečnost nekončí odesláním kódu. PHP i všechny závislosti držím aktuální a aplikaci nasazuji zásadně přes platný SSL certifikát. Základ produkčního nastavení a rychlou kontrolu závislostí zvládnete pár řádky:

# Produkční nastavení v .env
APP_ENV=production
APP_DEBUG=false

# Kontrola známých zranitelností v závislostech
composer audit
  • Pravidelný composer audit odhalí balíčky se známou zranitelností dřív, než je najde někdo jiný.
  • Cachování konfigurace přes config:cache zrychlí a zároveň zpřehlední nasazení.
  • Nahrané soubory ukládám mimo veřejnou složku a hlídám jejich skutečný typ, ne jen příponu.

Kromě audit příkazu se vyplatí i pořádné logování. Když se přece jen něco stane, chci v logu vidět, kdo se kdy odkud přihlásil a která akce selhala — bez toho se po incidentu jen dohadujete. Neúspěšné přihlášení, změnu hesla i přístup k citlivým datům proto zaznamenávám, ale hlídám, aby se do logu nikdy nedostalo samotné heslo ani token. Log plný tajemství je totiž jen další místo, které vám může uniknout.

Přiznám na rovinu, kde to přehánět nemá smysl: interní nástroj pro tři kolegy za firemním přihlášením nepotřebuje stejnou paranoiu jako veřejný e-shop se statisíci zákazníků. Míru zabezpečení vždycky přizpůsobuji tomu, co je v sázce a kdo se k aplikaci vůbec dostane — nemá cenu stavět trezor kolem seznamu obědů. Základní hygienu jako escapování, parametrizované dotazy a aktuální závislosti ale dělám všude stejně, protože nic nestojí a ušetří spoustu nervů. Bezpečnost totiž skoro nikdy nepadne na jedné velké díře, ale na součtu drobných zanedbání, která se sešla ve špatnou chvíli.

Chcete aplikaci, která obstojí i pod tlakem? Napište mi a projdeme zabezpečení vašeho projektu.