Pierwsza część serii uporządkowała fundamenty: serwer, bazę danych, API i CORS. Teraz przechodzimy do obszarów, które zwykle pojawiają się chwilę później, gdy aplikacja przestaje być prostym CRUD-em: komunikacja w czasie rzeczywistym, webhooki oraz autentykacja.
To są tematy, w których frontend i backend bardzo mocno na siebie wpływają. Wybór między SSE a WebSocketami zmienia sposób budowania UI. Źle obsłużony webhook potrafi podwoić zamówienie. Niejasny model auth kończy się losowymi 401, problemami z cookie i debugowaniem, które wygląda jak szukanie błędu po omacku.
Krótka odpowiedź: używaj SSE, gdy serwer tylko wysyła aktualizacje do klienta, a WebSockets, gdy komunikacja ma iść w obie strony. Webhooki zawsze weryfikuj podpisem, zapisuj idempotentnie i odpowiadaj szybko. W auth rozdziel autentykację od autoryzacji, świadomie wybieraj sesje albo JWT i traktuj cookies, CSRF oraz refresh tokeny jako część architektury, a nie detal implementacyjny.
1. Real-time — WebSocket, SSE, polling
REST dobrze obsługuje klasyczny model „zapytaj i dostań odpowiedź”. Nie wystarcza jednak wtedy, gdy serwer ma sam poinformować użytkownika o zmianie: nowej wiadomości, statusie zadania, postępie generowania odpowiedzi AI albo aktualizacji dashboardu. Wtedy wchodzą polling, SSE i WebSockets.
Polling — najprostsze rozwiązanie
Polling jest najprostszy mentalnie: frontend pyta co kilka sekund, czy coś się zmieniło. Nie wymaga specjalnej infrastruktury, ale płacisz za to opóźnieniem i dodatkowymi requestami.
Long polling — request wisi otwarty, dopóki backend nie ma czego zwrócić:
Long polling działa prawie wszędzie i bywa dobrym fallbackiem, ale w nowych projektach zwykle warto najpierw rozważyć SSE albo WebSockets.
Server-Sent Events (SSE)
SSE to jednokierunkowy strumień serwer → klient. Działa na zwykłym HTTP i ma automatyczny reconnect, więc świetnie pasuje do sytuacji, w których klient nie musi odsyłać wiadomości tym samym kanałem.
Idealne dla: powiadomień, feedów, AI streaming (ChatGPT-style), live progress.
WebSockets
WebSocket tworzy dwukierunkowy, trwały kanał. To już nie jest zwykły request-response, tylko osobny protokół (ws:// / wss://) z handshake'iem opartym na HTTP Upgrade.
Idealne dla: chatów, gier, kolaboracji w czasie rzeczywistym (Figma, Notion, Google Docs), live trading.
Wybór
Jeżeli potrzebujesz tylko wysyłać aktualizacje z serwera do klienta, SSE jest prostsze. Jeżeli klient i serwer mają rozmawiać w obie strony w czasie rzeczywistym, wybierz WebSockets.
| Scenariusz | Rozwiązanie |
|---|---|
| Powiadomienia "nowy komentarz" | SSE |
| Chat 1-on-1 / pokoje | WebSockets |
| AI streaming (ChatGPT-like) | SSE |
| Real-time gra | WebSockets |
| Status zadania w tle | SSE / polling |
| Kolaboracja na dokumencie (CRDT) | WebSockets |
Popularne biblioteki: Socket.IO (abstrakcja nad WS z fallbackami), Pusher (managed), Ably, Soketi (self-hosted Pusher), Cloudflare Durable Objects.
Wyzwania real-time
- Skalowanie horyzontalne — drugi serwer nie wie o połączeniach pierwszego. Rozwiązanie: Redis pub/sub jako szyna komunikacyjna.
- Autoryzacja — token w query stringu (loguje się w access logach), ale lepiej w pierwszej wiadomości po connect.
- Sticky sessions — load balancer musi trzymać klienta przy tym samym serwerze.
- Reconnect logic — klient powinien wznowić od ostatniego eventu (
Last-Event-IDw SSE).
2. Webhooks — backend powiadamia inny system
Webhook to odwrócony API call. Zamiast co minutę pytać system płatności „czy faktura jest już opłacona?”, pozwalasz mu samemu poinformować Twoją aplikację, gdy coś się wydarzy. To prosty wzorzec, ale wymaga dyscypliny: podpisy, retry, idempotency i szybka odpowiedź są tutaj krytyczne.
Trzy zasady webhooks
1. Weryfikuj podpis
Dostawca podpisuje payload (najczęściej HMAC-SHA256 z sekretem). Bez weryfikacji ktoś może udawać Stripe i oznaczać zamówienia jako opłacone:
2. Odpowiadaj szybko (< 2s)
Webhook to nie miejsce na ciężką logikę. Dostawca ma timeout (Stripe: 30s, ale prowadzi to do retry). Wzorzec: enqueue + 200 OK:
3. Idempotency — przygotuj się na duplikaty
Ten sam webhook może przyjść kilka razy (retry przy timeout). Zapisuj event.id w bazie i ignoruj duplikaty:
Lokalne testowanie
Webhook nie dojdzie bezpośrednio na localhost:3000, bo zewnętrzny system musi mieć publiczny adres, pod który może wysłać request. Do lokalnego developmentu używa się tuneli:
- ngrok —
ngrok http 3000daje publiczny URL - Stripe CLI —
stripe listen --forward-to localhost:3000/webhooks/stripe - Cloudflare Tunnel — alternatywa dla ngrok
- smee.io — proxy używane przez GitHub
Częste źródła webhooks
- Stripe — płatności (
payment_intent.succeeded,invoice.paid) - GitHub —
push,pull_request,issues - Resend / SendGrid — delivery, bounces, complaints
- Slack — slash commands, events
- Twilio — SMS delivery, calls
- Clerk / Auth0 — user lifecycle (signup, deleted)
- Vercel — deployment events
3. Autoryzacja i autentykacja
To są dwa różne pojęcia, które często wrzuca się do jednego worka. Autentykacja odpowiada na pytanie „kim jesteś?”, a autoryzacja na pytanie „co możesz zrobić?”. Frontend odczuwa oba mechanizmy przez logowanie, wygasające sesje, ukrywanie elementów UI i błędy 401 / 403, ale prawdziwa decyzja zawsze musi zapaść na backendzie.
Autentykacja — "kim jesteś?"
Autentykacja to weryfikacja tożsamości użytkownika.
Metody:
- Hasło — tradycyjne logowanie
- OAuth to standard delegowanego dostępu, który pozwala logować użytkownika przez zewnętrznego dostawcę bez ujawniania hasła. — "Zaloguj przez Google/GitHub"
- Magic link — link logowania na email
- Passkeys — biometria, hardware keys
Autoryzacja — "co możesz robić?"
Autoryzacja to sprawdzanie uprawnień. UI może ukryć przycisk „Usuń użytkownika”, ale backend i tak musi sprawdzić rolę przy samym requeście.
JWT (JSON Web Token)
JWT to format tokenu, a nie magiczny zamiennik sesji. Często sprawdza się w API, aplikacjach mobilnych i integracjach między usługami, ale w klasycznych aplikacjach webowych sesje nadal bywają prostsze i bezpieczniejsze operacyjnie.
Flow:
- User loguje się → serwer tworzy krótko żyjący token
- Frontend trzyma go bezpiecznie (np. w pamięci) albo korzysta z mechanizmu ciasteczek (cookies)
- Przeglądarka wysyła token w nagłówku lub cookie
- Serwer weryfikuje podpis albo identyfikator sesji przy każdym request
Sessions
Alternatywa dla JWT — stan przechowywany na serwerze:
JWT vs sesje — kompromisy
| Cecha | Sesja | JWT |
|---|---|---|
| Stan | Na serwerze (Redis/DB) | W tokenie (stateless) |
| Wylogowanie | Natychmiast (delete sesji) | Trudne — token żyje do exp |
| Skalowanie | Trzeba współdzielić storage | Idealnie skaluje się stateless |
| Rozmiar requestu | Małe ID | Cały token w każdym requeście |
| Mobile / native | OK | Często wybierane (brak cookies) |
| Cross-domain | Trudne | Łatwe |
Reguła kciuka: monolit / web → sesje, API mikrousług / mobile → JWT (z krótkim TTL i refresh rotation).
Cookies — szczegóły, które mają znaczenie
Cookies wyglądają niepozornie, ale przy autentykacji każdy flag ma znaczenie. Jedna błędna konfiguracja potrafi zrobić różnicę między rozsądnym modelem bezpieczeństwa a tokenem, który wycieka przez XSS albo jest wysyłany w niepożądanym kontekście.
| Flag | Co robi | Po co |
|---|---|---|
| HttpOnly | JS nie ma dostępu (document.cookie) | Ochrona przed kradzieżą tokena przez XSS |
| Secure | Wysyłane tylko po HTTPS | Brak transmisji plaintext |
| SameSite=Strict | Cookie tylko z requestów z tego samego site | Pełna ochrona CSRF, ale ubija UX (np. kliknięcie z e-maila wymaga ponownego loginu) |
| SameSite=Lax (domyślne) | Cookie idzie z top-level GETami z innych site, ale nie z formularzami POST | Dobry default |
| SameSite=None | Cookie idzie zawsze (wymaga Secure) | Embedy iframe, third-party |
| Path | Tylko dla podanego path | Ograniczenie zasięgu |
| Domain | Subdomeny mogą czytać | .example.com = wszystkie subdomeny |
| Max-Age / Expires | TTL | Po wygaśnięciu cookie znika |
CSRF — kuzyn XSS
Jeśli używasz cookies do auth, musisz rozumieć CSRF (Cross-Site Request Forgery). Atakujący nie musi znać tokena użytkownika. Wystarczy, że użytkownik jest zalogowany, a przeglądarka automatycznie dołączy cookie do requestu wysłanego z obcej strony.
Przeglądarka sama dołączy cookie auth do tego POST-a, jeśli SameSite na to pozwala.
Obrona (warstwy):
SameSite=LaxlubStrictna cookie auth — chroni 95% przypadków automatycznie.- CSRF token (double-submit cookie) — backend generuje token, frontend odczytuje go i dokleja w headerze do requestów zmieniających stan. Atakująca strona nie może odczytać Twojego cookie (Same-Origin Policy).
- Sprawdzaj
Origin/Referer— backend odrzuca requesty zmieniające stan, jeśliOriginnie pasuje do allowlisty.
JWT w localStorage jest odporny na CSRF (przeglądarka nie wysyła go automatycznie), ale podatny na XSS — kompromis idzie w drugą stronę. Best practice: JWT w cookie z HttpOnly + SameSite=Strict.
Refresh token rotation
Krótki access token (15 min) + długi refresh token (dni / tygodnie). Frontend odświeża access w tle, zanim wygaśnie.
Rotation detection — jeśli ktoś użyje starego, już rotowanego refresh tokena, system zakłada kradzież i unieważnia całą rodzinę tokenów (wszystkie sesje usera). Legalny user zostanie wylogowany — ale bezpiecznie.
MFA — drugi czynnik
Hasło to wiedza ("co znasz"). MFA dodaje "co masz" lub "kim jesteś":
- TOTP — Google Authenticator / Authy (RFC 6238, kod 30s)
- WebAuthn / Passkeys — klucze sprzętowe (YubiKey) lub biometria (Touch ID, Face ID)
- SMS — wygodne, ale podatne na SIM swap
- Push notifications — Microsoft Authenticator, Duo
Passkeys to przyszłość — żadnego hasła, tylko biometria + private key na urządzeniu. Już wspierane przez Apple, Google, Microsoft, GitHub.
Gotowe rozwiązania
Auth jest jednym z tych obszarów, gdzie „napiszę sam, będzie szybciej” bardzo rzadko kończy się dobrze. W produkcji korzystaj ze sprawdzonych narzędzi albo przynajmniej z dojrzałych bibliotek.
- Auth.js / NextAuth — Next.js, otwarte
- Clerk — managed, świetne UX, drogie przy skali
- Supabase Auth — w pakiecie z bazą
- Auth0 / Okta — enterprise
- Lucia — minimalne, type-safe, self-hosted
- Better Auth — nowoczesne, framework-agnostic
Co dalej w serii?
Po tej części masz już obraz tego, jak backend utrzymuje kontakt z użytkownikiem i zewnętrznymi systemami: strumienie zdarzeń, webhooki, sesje, tokeny i uprawnienia.
W trzeciej części przechodzimy do utrzymania aplikacji w produkcji: cache, kolejek, uploadu plików, deploymentu, monitoringu i bezpieczeństwa.
- Backend dla frontendowca: serwer, bazy danych i API
- Backend dla frontendowca: cache, deployment i bezpieczeństwo
