Přeskočit na hlavní obsah

Autentizace

Tento dokument popisuje technickou implementaci autentizačních mechanismů v DnA Cruises systému.

Přehled​

Systém podporuje několik metod autentizace:

  • Email + heslo - klasické přihlášení s podporou 2FA
  • Google OAuth - přihlášení přes Google účet
  • Passkeys (WebAuthn) - bezpečné přihlášení pomocí biometrie nebo PINu
  • 2FA (Two-Factor Authentication) - dodatečná vrstva zabezpečení pomocí TOTP nebo SMS

JWT Autentizace​

Architektura​

Všechny metody autentizace nakonec generují JWT (JSON Web Token), který se používá pro autorizaci dalších požadavků.

Token struktura​

JWT token obsahuje následující claims:

  • userId - ID uživatele
  • email - Email uživatele
  • role - Role uživatele (Admin, Producer, Loader)
  • iat - Issued at (čas vydání)
  • exp - Expiration (čas expirace, defaultně 7 dní)

Token lifecycle​

  1. Generování: Token je generován po úspěšném přihlášení pomocí generateJWT() funkce
  2. Uložení: Frontend ukládá token do localStorage pod klíčem auth_token
  3. Odesílání: Token se odesílá v Authorization headeru jako Bearer <token>
  4. Validace: Middleware requireAuth() validuje token při každém požadavku
  5. Obnova: Token se automaticky obnovuje při každém úspěšném požadavku (pokud je blízko expirace)

Implementace​

Backend (workers/api/src/auth/jwt.ts):

  • generateJWT() - generuje token s HS256 algoritmem
  • verifyJWT() - ověřuje token a extrahuje payload

Frontend (apps/web/src/hooks/useAuth.tsx):

  • AuthProvider - React context pro správu autentizačního stavu
  • useAuth() - hook pro přístup k autentizačním funkcím
  • Automatické načítání tokenu z localStorage při startu aplikace

Passkeys (WebAuthn)​

Architektura​

Passkeys používají WebAuthn standard (FIDO2) pro bezpečné přihlášení bez hesla. Systém podporuje:

  • Registraci - vytvoření nového passkey pro uživatele
  • Autentizaci - přihlášení pomocí existujícího passkey
  • Správu - zobrazení, přejmenování a mazání passkeys

Registrace flow​

  1. Start (POST /auth/passkey/register/start):

    • Server vygeneruje náhodný challenge
    • Challenge se uloží do databáze s expirací (5 minut)
    • Server vrátí PublicKeyCredentialCreationOptions
  2. Browser:

    • Uživatel potvrdí registraci (biometrie, PIN, atd.)
    • Browser vytvoří PublicKeyCredential s attestation objectem
  3. Finish (POST /auth/passkey/register/finish):

    • Frontend odešle credential data na server
    • Server extrahuje public key z attestation objectu (CBOR parsing)
    • Server převede COSE public key na JWK formát
    • Server uloží credential ID a public key do databáze
    • Server ověří challenge a smaže ho

Autentizace flow​

  1. Start (POST /auth/passkey/login/start):

    • Pokud je zadán email, server vrátí seznam povolených credentials
    • Server vygeneruje challenge a uloží ho do databáze
  2. Browser:

    • Browser zobrazí seznam dostupných passkeys
    • Uživatel vybere passkey a potvrdí (biometrie, PIN)
    • Browser vytvoří assertion s podpisem
  3. Finish (POST /auth/passkey/login/finish):

    • Frontend odešle assertion data na server
    • Server načte public key z databáze
    • Server ověří podpis pomocí verifyAssertion():
      • Ověří clientDataJSON (type, challenge, origin)
      • Ověří authenticatorData (RP ID hash, user present flag, sign count)
      • Ověří kryptografický podpis pomocí ECDSA P-256
    • Pokud je vše v pořádku, server vygeneruje JWT token

Bezpečnostní aspekty​

  • Challenge: Každý request má unikátní, náhodný challenge (32 bytes)
  • Sign count: Server kontroluje, že sign count roste (ochrana proti replay útokům)
  • RP ID verification: Server ověřuje, že request přichází z očekávané domény
  • Origin verification: Server ověřuje origin v clientDataJSON
  • Public key extraction: Server extrahuje public key z attestation objectu (nevěří klientovi)

Implementace​

Backend (workers/api/src/utils/webauthn.ts):

  • generateChallenge() - generuje náhodný challenge
  • extractPublicKeyFromAttestation() - extrahuje public key z attestation objectu
  • verifyAssertion() - ověřuje assertion podpis
  • parseAuthenticatorData() - parsuje authenticator data
  • verifyES256Signature() - ověřuje ECDSA P-256 podpis

Frontend (apps/web/src/hooks/useAuth.tsx):

  • registerPasskey() - registruje nový passkey
  • loginWithPasskey() - přihlásí se pomocí passkey
  • getPasskeys() - načte seznam passkeys
  • deletePasskey() - smaže passkey
  • renamePasskey() - přejmenuje passkey

2FA (Two-Factor Authentication)​

Podporované metody​

  1. TOTP (Time-based One-Time Password)

    • RFC 6238 kompatibilní
    • 6místné kódy, 30 sekundový interval
    • Podporuje time window (±1 time step)
  2. SMS

    • 6místné kódy
    • Platnost 10 minut
    • Integrace s DinoSMS API

Setup flow​

  1. TOTP Setup:

    • Uživatel zavolá POST /auth/2fa/setup s method: "TOTP"
    • Server vygeneruje Base32 secret (20 bytes)
    • Server vytvoří otpauth:// URL pro QR kód
    • Uživatel naskenuje QR kód v autentizační aplikaci
    • Uživatel zavolá POST /auth/2fa/enable s kódem z aplikace
    • Server ověří kód pomocí verifyTOTP()
    • Pokud je kód platný, server povolí 2FA
  2. SMS Setup:

    • Uživatel zavolá POST /auth/2fa/setup s method: "SMS" a telefonním číslem
    • Server vygeneruje 6místný kód
    • Server odešle SMS přes DinoSMS API
    • Uživatel zavolá POST /auth/2fa/enable s kódem ze SMS
    • Server ověří kód
    • Pokud je kód platný, server povolí 2FA

Login flow s 2FA​

  1. Uživatel se přihlásí email + heslem
  2. Pokud má uživatel povolenou 2FA:
    • Server vytvoří dočasnou session (user_sessions tabulka)
    • Server vrátí requires2FA: true a session_token
    • Frontend zobrazí formulář pro 2FA kód
  3. Uživatel zadá 2FA kód
  4. Frontend zavolá POST /auth/2fa/verify s kódem a session tokenem
  5. Server ověří kód:
    • TOTP: použije verifyTOTP() s time window
    • SMS: porovná kód s uloženým kódem a zkontroluje expiraci
  6. Pokud je kód platný, server vygeneruje JWT token a smaže session

TOTP implementace​

Backend (workers/api/src/utils/totp.ts):

  • generateTOTPSecret() - generuje Base32 secret
  • generateTOTPCode() - generuje TOTP kód pro daný time counter
  • verifyTOTP() - ověřuje TOTP kód s time window tolerance
  • generateOTPAuthURL() - vytváří otpauth:// URL pro QR kód

Algoritmus:

  1. Decode Base32 secret na bytes
  2. Vypočítat time counter: floor(now / timeStep)
  3. Vytvořit 8-byte big-endian buffer z time counteru
  4. Vypočítat HMAC-SHA1(secret, time counter)
  5. Dynamic truncation (RFC 4226)
  6. Modulo 1000000 pro 6místný kód

SMS implementace​

Backend (workers/api/src/utils/dinosms.ts):

  • generateSMSCode() - generuje 6místný náhodný kód
  • sendSMS() - odesílá SMS přes DinoSMS API

Flow:

  1. Server vygeneruje kód a uloží ho do users.sms_code
  2. Server nastaví expiraci (sms_code_expires_at)
  3. Server odešle SMS přes DinoSMS API
  4. Po ověření se kód smaže z databáze

Bezpečnostní aspekty​

  • TOTP:

    • Constant-time comparison pro ochranu proti timing útokům
    • Time window tolerance (±1 time step = ±30 sekund)
    • Secret je uložen v databázi (hashovaný by byl lepší, ale pro TOTP není nutný)
  • SMS:

    • Kódy mají expiraci (10 minut)
    • Kódy se smažou po použití
    • Rate limiting by měl být implementován (TODO)

Google OAuth​

Flow​

  1. Uživatel klikne na "Přihlásit se přes Google"
  2. Frontend přesměruje na GET /auth/google
  3. Server vygeneruje state token (CSRF protection)
  4. Server uloží state do databáze (oauth_states tabulka)
  5. Server přesměruje na Google OAuth consent screen
  6. Uživatel autorizuje aplikaci
  7. Google přesměruje zpět na GET /auth/google/callback?code=...&state=...
  8. Server ověří state token
  9. Server vymění authorization code za access token
  10. Server získá user info z Google API
  11. Server vytvoří nebo najde uživatele v databázi
  12. Server vygeneruje JWT token
  13. Server přesměruje na frontend s tokenem v URL

Implementace​

Backend (workers/api/src/routes/oauth.ts):

  • GET /auth/google - iniciuje OAuth flow
  • GET /auth/google/callback - zpracovává OAuth callback

Frontend (apps/web/src/app/auth/callback/page.tsx):

  • Zpracovává redirect s tokenem v URL
  • Ukládá token do localStorage
  • Přesměruje na hlavní stránku

Middleware​

requireAuth()​

Middleware pro ochranu endpointů vyžadujících autentizaci.

Funkce:

  1. Extrahuje JWT token z Authorization headeru
  2. Ověří token pomocí verifyJWT()
  3. Načte uživatele z databáze
  4. Přidá auth objekt do requestu s userId, email, role

Použití:

routes.push({
method: 'GET',
path: '/protected',
handler: async (request, env) => {
const auth = (request as any).auth as AuthContext;
// auth.userId, auth.email, auth.role jsou dostupné
},
middleware: [requireAuth()],
});

Role-based access control​

Middleware podporuje kontrolu rolí:

  • requireAuth({ role: 'Admin' }) - pouze Admin
  • requireAuth({ roles: ['Admin', 'Producer'] }) - Admin nebo Producer

Databázové tabulky​

users​

  • id - primární klíč
  • email - unikátní email
  • password_hash - PBKDF2 hash hesla
  • role - Admin, Producer, nebo Loader
  • two_factor_enabled - boolean flag
  • two_factor_method - TOTP nebo SMS
  • totp_secret - Base32 secret pro TOTP
  • phone_number - telefonní číslo pro SMS
  • sms_code - dočasný SMS kód
  • sms_code_expires_at - expirace SMS kódu

user_passkeys​

  • id - primární klíč
  • user_id - reference na users.id
  • credential_id - base64url encoded credential ID
  • public_key - JSON string s public key JWK a metadata
  • name - uživatelsky definovaný název
  • counter - sign count pro replay protection
  • created_at - čas vytvoření
  • last_used_at - čas posledního použití

passkey_challenges​

  • id - primární klíč
  • challenge - base64url encoded challenge
  • user_id - reference na users.id (volitelné)
  • email - email pro login flow (volitelné)
  • type - 'register' nebo 'login'
  • expires_at - Unix timestamp expirace
  • created_at - čas vytvoření

oauth_states​

  • state - primární klíč (CSRF token)
  • user_id - reference na users.id (volitelné)
  • expires_at - Unix timestamp expirace
  • created_at - čas vytvoření

user_sessions​

  • id - primární klíč
  • session_token - unikátní session token
  • user_id - reference na users.id
  • two_factor_verified - boolean flag
  • expires_at - Unix timestamp expirace
  • created_at - čas vytvoření

Bezpečnostní best practices​

  1. Hesla: PBKDF2 s 100,000 iteracemi, SHA-256, 32-byte salt
  2. JWT: HS256 algoritmus, 7 denní expirace
  3. Passkeys: Server-side signature verification, sign count tracking
  4. 2FA: Constant-time comparison, time window tolerance
  5. OAuth: State token pro CSRF protection
  6. Sessions: Expirace po 10 minutách neaktivity
  7. Rate limiting: Mělo by být implementováno (TODO)

Troubleshooting​

Passkey registrace selhává​

  • Zkontrolujte, že prohlížeč podporuje WebAuthn
  • Zkontrolujte, že doména má HTTPS (nebo localhost)
  • Zkontrolujte console logy pro specifické chyby

2FA kód nefunguje​

  • TOTP: Zkontrolujte čas na serveru a zařízení (musí být synchronizovaný)
  • SMS: Zkontrolujte, že SMS dorazila (může trvat několik sekund)
  • Zkontrolujte, že kód není expirovaný

JWT token expiroval​

  • Token má expiraci 7 dní
  • Uživatel se musí znovu přihlásit
  • Frontend automaticky přesměruje na login page