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