Bezpečnost není funkce, kterou přidáš nakonec — je to způsob, jakým přemýšlíš o každém endpointu. Dvě věty, které když si zapamatuješ, vyhneš se většině začátečnických děr: nikdy nevěř tomu, co přijde od klienta, a nedávej nikomu víc oprávnění, než nezbytně potřebuje. Zbytek kapitoly tyhle dvě myšlenky rozvíjí.
Pár pojmů na úvod
- Autentizace (authentication) = ověření, kdo jsi (přihlášení heslem, tokenem…).
- Autorizace (authorization) = ověření, co smíš (máš právo na tuhle akci/data?).
- Token = digitální „lístek", kterým se po přihlášení prokazuješ u každého dalšího requestu.
- Hash = jednosměrná přeměna textu na otisk — z hesla spočítáš otisk, ale z otisku heslo zpět nezískáš.
- Útočník = kdokoli, kdo zkouší tvůj systém zneužít (a předpokládej, že to zkouší).
Autentizace vs autorizace (nepleť si)
- Autentizace (AuthN) = „kdo jsi?" Když selže → HTTP
401. - Autorizace (AuthZ) = „co smíš?" Když selže → HTTP
403.
Pořadí: nejdřív zjisti, kdo uživatel je (AuthN), pak co smí (AuthZ). A obojí na serveru, u každé operace, co mění data. (Rozdíl 401 vs 403 znáš z Foundations.)
RBAC vs ABAC — jak řídit, kdo co smí
Jakmile řešíš autorizaci pro víc lidí, potřebuješ systém, jak práva přidělovat:
- RBAC (Role-Based Access Control) — práva se přidělují přes role. Uživatel má roli (
admin,editor,viewer) a role určuje, co smí. Jednoduché a stačí na většinu aplikací. - ABAC (Attribute-Based Access Control) — práva se odvozují z atributů (vlastností uživatele, zdroje, kontextu): „editor smí upravit dokument, jen pokud patří do jeho oddělení a je pracovní doba". Mocnější, ale složitější.
Začni s RBAC (role). Po ABAC sáhni, až ti pravidla přerostou jednoduché role. A ať použiješ cokoli, kontrola se musí dít na serveru a pro každý zdroj (jinak hrozí IDOR, viz níže).
Session vs token (JWT)
Po přihlášení si musí server nějak pamatovat, že jsi to ty. Dva přístupy:
| Session (na serveru) | Token / JWT (u klienta) | |
|---|---|---|
| Kde je stav | Server si drží záznam o přihlášení | Stav nese sám token (server si nic nepamatuje) |
| Odhlášení / zneplatnění | Smažeš session → hned neplatí | Těžké — token platí, dokud nevyprší |
JWT (čti „džot") je takový token. Je podepsaný (server pozná, že ho nikdo nezměnil), ale není šifrovaný → nedávej do něj nic tajného, protože se dá přečíst. Aby se omezila škoda při krádeži, používá se krátká platnost (token brzy vyprší) plus „obnovovací" token, který jde zneplatnit.
⚠️ „Podepsaný" chrání jen když server podpis fakt ověří správným algoritmem a klíčem. Klasické JWT díry: token s
alg: none(žádný podpis — server ho nesmí přijmout) a záměna RS256/HS256 (útočník podepíše token veřejným klíčem). Proto nikdy nepiš ověřování JWT ručně, použij ověřenou knihovnu a zafixuj povolený algoritmus.
OAuth / „přihlášení přes Google"
Když appka nabízí „přihlásit se přes Google/GitHub", používá k tomu OAuth — uživatel se přihlásí u Googlu a ten tvojí appce potvrdí, kdo to je, aniž bys ty kdy viděl jeho heslo. Pro tebe jako juniora hlavní pravidlo: OAuth nikdy neimplementuj od nuly, vždy použij ověřenou knihovnu — je to plné záludností, ve kterých se snadno udělá bezpečnostní díra.
Service-to-service auth — kdo ověří službu, ne uživatele
Doteď jsme řešili, jak ověřit uživatele. Ale jak ví služba A, že požadavek opravdu přišel od služby B, a ne od útočníka uvnitř sítě? „Je to z naší sítě, takže je to OK" je nebezpečný předpoklad (zero trust říká: nevěř ani vnitřní síti). Řeší se to mTLS (mutual TLS) — na rozdíl od běžného HTTPS, kde se certifikátem prokazuje jen server, se u mTLS prokazují obě strany, takže si služby navzájem ověří identitu. Ve větších systémech to obstarává service mesh / workload identita (SPIFFE). Pro juniora stačí znát pojem a princip „služby se ověřují taky, ne jen uživatelé".
OWASP Top 10 — nejčastější díry
OWASP je organizace, která udržuje seznam nejčastějších bezpečnostních chyb. Pár, které musíš znát:
- Broken Access Control (porušená kontrola přístupu) — uživatel se dostane k cizím datům.
Nejčastější forma je IDOR: endpoint
/orders/123vrátí objednávku jen podle ID, tak si zkusím změnit URL na/orders/124a vidím cizí objednávku. Obrana: na serveru vždy ověř, že data patří přihlášenému uživateli, ne jen že je přihlášený. - Injection (vstříknutí kódu) — vstup od uživatele se omylem provede jako kód. Klasika je
SQL injection, kde vstup změní tvůj databázový dotaz. Obrana: parametrizované dotazy / ORM
(vstup se předá jako data, ne jako součást příkazu) — nikdy neslepuj dotaz z textu od uživatele:
// ❌ ŠPATNĚ — slepený řetězec: vstup `' OR '1'='1` ti vrátí všechny uživatele db.query(`SELECT * FROM users WHERE email = '${email}'`) // ✅ SPRÁVNĚ — parametr: databáze bere `email` vždy jako data, nikdy jako SQL db.query('SELECT * FROM users WHERE email = $1', [email]) - XSS (Cross-Site Scripting) — útočník vloží do stránky
<script>, který se spustí v prohlížeči jiného uživatele. Obrana: výstup vždy „escapuj" (zneškodni HTML znaky) a nikdy nevkládej do stránky neošetřený text od uživatele jako HTML. - CSRF — cizí web pošle požadavek jménem tvého přihlášeného uživatele. Týká se hlavně autentizace
přes cookie (prohlížeč ji přiloží sám); u API s tokenem v hlavičce
AuthorizationCSRF obvykle nevzniká. Obrana: CSRF token.SameSite=Laxdnes nastavují prohlížeče defaultně, ale sám nestačí (nechrání všechny případy), takže u operací měnících stav stejně potřebuješ token.
Hesla a tajné údaje
- Hesla NIKDY neukládej v čitelné podobě ani jako „obyčejný" rychlý hash (SHA-256). Použij bcrypt / argon2 / scrypt — jsou záměrně pomalé (zkoušet hesla po milionech se nevyplatí) a používají salt (náhodnou přísadu pro každého uživatele zvlášť, takže stejné heslo má jiný otisk). Detaily, co odlišují seniora: OWASP doporučuje variantu argon2id; bcrypt tiše ořízne heslo nad 72 bajtů (reálná díra); a navíc pepper (tajný klíč mimo databázi, přidaný ke každému heslu) tě chrání i v případě, že někdo ukradne celou databázi i se salty.
- Secrets (API klíče, hesla k databázi) patří do konfigurace mimo kód (proměnné prostředí, secret manager) a nikdy do gitu — z historie se už nedají pořádně smazat.
- Šifruj data jak při přenosu (HTTPS/TLS), tak v úložišti (disk/databáze).
Validace, rate limiting a povrch útoku
- Validuj vstup na serveru, i když ho ověřuje i frontend — klient se vždy dá obejít. Lepší je whitelist („povol jen tohle") než blacklist („zakaž tohle").
- Rate limiting (Patterny) brání hádání hesel a zahlcení →
429. - Princip nejmenších oprávnění (least privilege) — každý proces, uživatel i token má jen to, co opravdu potřebuje. Když ho někdo zneužije, napáchá míň škody.
- Defense in depth (obrana do hloubky) — víc vrstev ochrany: když jedna selže, chytne to další.
- Nezveřejňuj v chybách technické detaily (stack trace, SQL, verze knihoven) — útočníkovi to pomáhá.
Secrets, audit log a supply chain
Pár dalších věcí, které k bezpečnému backendu patří:
- Secrets management — tajné údaje (klíče, hesla k databázi) nepatří do kódu ani do gitu. Drž je v proměnných prostředí, nebo líp v secret manageru (specializovaná služba), kde se dají rotovat (pravidelně měnit) a sledovat, kdo k nim sáhl. Když klíč unikne, vyměň ho. ⚠️ Rotuj bez výpadku: nový klíč naivně shodí všechny instance, co drží starý. Proto ať starý i nový klíč chvíli platí současně (overlapping validity) — nasaď nový, přepni produkci, teprve pak starý zruš. Je to přesně stejný princip jako expand-contract u databázových migrací.
- Audit log — záznam o citlivých akcích („kdo, kdy, co změnil/smazal"). Když se něco stane, chceš vědět, kdo za tím byl. Ukládej ho odděleně a tak, aby nešel snadno přepsat.
- CSP (Content Security Policy) — bezpečnostní hlavička, kterou řekneš prohlížeči, odkud smí načítat skripty. Je to další obrana proti XSS: i když útočník skript propašuje, prohlížeč ho nespustí, pokud není z povoleného zdroje.
- Supply chain (dodavatelský řetězec) — tvoje appka závisí na stovkách cizích knihoven a kterákoli
může mít díru, nebo být přímo napadená (kompromitovaný maintainer, typosquatting — reálný případ
je backdoor v
xz/liblzma z roku 2024). Proto: skenuj závislosti na známé zranitelnosti, pinuj verze (lockfile, hashe), aktualizuj a u větších projektů veď SBOM (Software Bill of Materials — seznam, co všechno reálně používáš; dnes i regulatorní požadavek). Cizí kód je tvoje odpovědnost.
A jeden způsob myšlení nad tím vším: threat modeling. Než něco postavíš, projdi „kdo je útočník, kde jsou hranice důvěry (trust boundaries) a co se může pokazit" (metoda STRIDE). Líp najít díru na papíře než v produkci.
Failure modes — nejčastější reálné díry
- IDOR → endpoint ověří jen přihlášení, ne vlastnictví dat. Hodně častá chyba.
- SQL injection → dotaz slepený z textu místo parametrů.
- Token bez vypršení / nejde zneplatnit → ukradený token platí navždy.
- Secrets v gitu → uniknou natrvalo (i z historie). Když se to stane, klíče vyměň.
- Důvěra klientovi → validace jen na frontendu = žádná validace.
🛠️ Cvičení
- Najdi IDOR. Endpoint
GET /orders/123vrací objednávku jen podle ID. Popiš, jak ho zneužiju, a oprav to ověřením vlastnictví na serveru. - Oprav SQL injection. Máš dotaz slepený z textu od uživatele. Přepiš ho na parametrizovaný a vysvětli, proč to injection zabrání.
- 401, nebo 403? U pěti situací urči správný kód: špatné heslo, vypršelý token, běžný uživatel chce admin stránku, neznámý uživatel, platný token bez oprávnění k akci.
- Hashování hesel. Vysvětli, proč
SHA-256(heslo)nestačí, a co dělají bcrypt/argon2 jinak (salt, pomalost). - Mini threat model. Pro nahrání profilové fotky vyjmenuj tři rizika (typ souboru, velikost, stažení z cizí URL) a obranu ke každému.
Náčrt řešení — rozbal, až si cvičení zkusíš sám
- Najdi IDOR —
GET /orders/123vrací objednávku jen podle ID, takže si změním URL na/orders/124a vidím cizí objednávku; oprava je na serveru u každého requestu ověřit, že objednávka patří přihlášenému uživateli (WHERE id = ? AND user_id = ?), ne jen že je přihlášený. Pozor: nestačí ověřit autentizaci (že je přihlášený) — chybí autorizace na konkrétní zdroj, a tohle je jedna z nejčastějších reálných děr. - Oprav SQL injection — dotaz slepený z textu (
"... WHERE name = '" + vstup + "'") přepiš na parametrizovaný (... WHERE name = ?a vstup předej zvlášť jako parametr) nebo použij ORM; databáze pak vstup vezme jako data, nikdy jako součást příkazu. Pozor: nikdy neslepuj dotaz z textu od uživatele — a dej DB účtu jen nezbytná práva (least privilege), ať případný průnik napáchá míň škody. - 401, nebo 403? — špatné heslo →
401(neověřená identita), vypršelý token →401, neznámý uživatel →401; běžný uživatel na admin stránku →403(ví se kdo je, ale nesmí), platný token bez oprávnění k akci →403. Pozor: 401 = „nevím, kdo jsi / neověřil ses", 403 = „vím kdo jsi, ale nesmíš" — pleteni těchhle dvou je klasická chyba. - Hashování hesel —
SHA-256(heslo)je rychlá a bez soli, takže útočník zkusí miliardy hesel za sekundu a předpočítané tabulky prolomí běžná hesla okamžitě; bcrypt/argon2 jsou záměrně pomalé (zkoušení se nevyplatí) a používají salt (náhodná přísada pro každého zvlášť → stejné heslo má jiný otisk). Pozor: doporučená varianta je argon2id a bcrypt tiše ořízne heslo nad 72 bajtů — drobnost, na které senior pozná juniora. - Mini threat model — typ souboru: útočník nahraje skript místo obrázku → ověřuj reálný obsah/MIME (ne jen příponu) a povol whitelist formátů; velikost: obří soubor zahltí úložiště → limit velikosti a
429/odmítnutí; stažení z cizí URL: zadání interní adresy (SSRF) → URL nestahuj naivně, validuj a blokuj interní rozsahy. Pozor: spoléhej na whitelist („povol jen tohle") místo blacklistu a validuj na serveru — klient se vždy dá obejít.
🧠 Otázky & odpovědi
Jaký je rozdíl mezi autentizací a autorizací?
Autentizace ověřuje „kdo jsi" (heslo, token) → při selhání 401. Autorizace řeší „co smíš"
(role, vlastnictví dat) → při selhání 403. Pořadí: nejdřív zjisti, kdo uživatel je, pak co smí. Obojí
se musí dít na serveru a u každé operace, co mění data — frontend se dá obejít.
Co je IDOR a jak ho ošetříš?
IDOR (Insecure Direct Object Reference): endpoint vrátí data podle ID, ale neověří, že na ně má
uživatel právo — změním /orders/123 na 124 a vidím cizí objednávku. Ošetření: na serveru vždy ověř
vlastnictví / oprávnění (objednávka patří přihlášenému uživateli), ne jen to, že je přihlášený. Je
to jedna z nejčastějších reálných děr.
Proč nestačí hesla hashovat přes SHA-256?
SHA-256 je rychlá a bez soli → útočník zkusí miliardy hesel za sekundu a předpočítané tabulky prolomí běžná hesla okamžitě. Použij bcrypt/argon2/scrypt: jsou záměrně pomalé (zkoušení hesel je drahé) a používají salt (náhodnou přísadu pro každého zvlášť, takže stejné heslo má pokaždé jiný otisk).
Jak se bránit SQL injection?
Parametrizovanými dotazy nebo ORM — vstup se předá jako parametr, ne jako součást textu dotazu, takže ho databáze nikdy nevezme jako příkaz. Nikdy neslepuj dotaz z textu od uživatele. Navíc validuj vstup a dej databázovému účtu jen nezbytná práva (least privilege), aby případný průnik napáchal míň škody.
Co znamená nevěřit ničemu, co přijde od klienta?
Cokoli přijde z prohlížeče nebo mobilu, může být podvržené — validace na frontendu je jen pohodlí pro uživatele, ne bezpečnost. Proto validuj a kontroluj oprávnění na serveru, i když to ověřuje i klient. Schované tlačítko v UI není ochrana; ochranou je serverová kontrola přímo na endpointu.
Jaký je rozdíl mezi RBAC a ABAC?
RBAC (role-based) přiděluje práva přes role — uživatel má roli (admin, editor) a ta určuje,
co smí. Jednoduché, stačí na většinu aplikací. ABAC (attribute-based) odvozuje práva z atributů
uživatele, zdroje a kontextu („editor smí upravit dokument, jen pokud je z jeho oddělení"). Mocnější,
ale složitější. Začni s RBAC, po ABAC sáhni, až pravidla přerostou jednoduché role.
Kam patří tajné údaje (secrets) a co je rotace?
Tajné údaje (API klíče, hesla k databázi) nepatří do kódu ani do gitu — odtud se už pořádně nesmažou. Patří do proměnných prostředí, líp do secret manageru, kde se dají bezpečně uložit, sledovat přístup a rotovat = pravidelně měnit, aby starý uniklý klíč brzy přestal platit. Když nějaký secret unikne, okamžitě ho vyměň.
