Security

AuthN/AuthZ, JWT, OWASP, IDOR, hashování hesel.

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 stavServer 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:

  1. 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/123 vrátí objednávku jen podle ID, tak si zkusím změnit URL na /orders/124 a 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ý.
  2. 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])
    
  3. 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.
  4. 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 Authorization CSRF obvykle nevzniká. Obrana: CSRF token. SameSite=Lax dnes 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í

  1. Najdi IDOR. Endpoint GET /orders/123 vrací objednávku jen podle ID. Popiš, jak ho zneužiju, a oprav to ověřením vlastnictví na serveru.
  2. Oprav SQL injection. Máš dotaz slepený z textu od uživatele. Přepiš ho na parametrizovaný a vysvětli, proč to injection zabrání.
  3. 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.
  4. Hashování hesel. Vysvětli, proč SHA-256(heslo) nestačí, a co dělají bcrypt/argon2 jinak (salt, pomalost).
  5. 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
  1. Najdi IDORGET /orders/123 vrací objednávku jen podle ID, takže si změním URL na /orders/124 a 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.
  2. 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.
  3. 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.
  4. Hashování heselSHA-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.
  5. 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ěň.