StriveLab
Strony internetowe
Usługi
RealizacjeO mnieBlogPorozmawiajmy
PL
EN

Astro

Ultraszybkie projekty, łączące lekkość ze skalowalnością.

Next.js

Elastyczne i wydajne narzędzia dla biznesu, które dotrzymają kroku Twojemu rozwojowi.

React

Połączenie intuicyjności z wydajnością, które zapewnia bezproblemową skalowalność kodu.

SEO & Performance

Audyt techniczny i optymalizacja pod kątem SEO i GEO.

Automatyzacja AI

Bezpieczne automatyzacje procesów i agenci AI w n8n, Make i Claude.

QA & Automation

Testy automatyczne komponentów i E2E w Cypress.

Konsultacje

Połączenie perspektywy produktu, developera i marketingu w jednym miejscu

StriveLab
Strony internetowe
Usługi
RealizacjeO mnieBlogPorozmawiajmy
PL
EN

Astro

Ultraszybkie projekty, łączące lekkość ze skalowalnością.

Next.js

Elastyczne i wydajne narzędzia dla biznesu, które dotrzymają kroku Twojemu rozwojowi.

React

Połączenie intuicyjności z wydajnością, które zapewnia bezproblemową skalowalność kodu.

SEO & Performance

Audyt techniczny i optymalizacja pod kątem SEO i GEO.

Automatyzacja AI

Bezpieczne automatyzacje procesów i agenci AI w n8n, Make i Claude.

QA & Automation

Testy automatyczne komponentów i E2E w Cypress.

Konsultacje

Połączenie perspektywy produktu, developera i marketingu w jednym miejscu

Astro

Ultraszybkie projekty, łączące lekkość ze skalowalnością.

Next.js

Elastyczne i wydajne narzędzia dla biznesu, które dotrzymają kroku Twojemu rozwojowi.

React

Połączenie intuicyjności z wydajnością, które zapewnia bezproblemową skalowalność kodu.

SEO & Performance

Audyt techniczny i optymalizacja pod kątem SEO i GEO.

Automatyzacja AI

Bezpieczne automatyzacje procesów i agenci AI w n8n, Make i Claude.

QA & Automation

Testy automatyczne komponentów i E2E w Cypress.

Konsultacje

Połączenie perspektywy produktu, developera i marketingu w jednym miejscu

RealizacjeO mnieBlog
Porozmawiajmy
PL
EN

Nowoczesne strony internetowe dla firm, które myślą odważnie.

Przewiń do góry

Nazwa

StriveLab Maciej Sala

NIP

6772218995

REGON

524008527

E-mail

contact@strivelab.pl

Usługi główne
  • Tworzenie stron internetowych
  • Strony internetowe Next.js
  • Strony internetowe Astro
  • Strony internetowe React
Inne usługi
  • Usługi
  • Audyt SEO i Performance
  • Testy automatyczne i QA
  • Konsultacje Produktowe
  • Automatyzacja Procesów AI
  • Aplikacje webowe Next.js
  • Współpraca ciągła
Strony
  • O mnie
  • Usługi
  • Realizacje
  • Blog

© 2026 StriveLab.pl

Polityka prywatności
Backend

Backend dla frontendowca: auth, real-time i integracje

Druga część serii Backend dla frontendowca: SSE, WebSockets, polling, webhooki, integracje zewnętrzne, sesje, JWT, cookies, CSRF, refresh token rotation i MFA.

OpublikujLinkedInFacebookWyślij
Autor
Maciej Sala
Opublikowano
29 lipca 2025 11:45
Czytanie
13 min czytania
Aktualizacja
11 czerwca 2026 10:00

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, integracje zewnętrzne oraz .

w skrócie

  • zamiast WebSocket — dla komunikacji serwer→klient SSE jest prostszy i nie wymaga osobnego serwera WebSocket; przy HTTP/1.x trzeba jednak uważać na limity połączeń.
  • = dwukierunkowy — wybierz gdy klient też musi wysyłać wiadomości (chat, multiplayer), nie dla prostych powiadomień.
  • Trzy zasady webhooków — weryfikuj sygnaturę HMAC, zapisuj idempotentnie, odpowiadaj 200 natychmiast i logikę przetwarzaj asynchronicznie w kolejce.
  • Autentykacja ≠ autoryzacja — autentykacja sprawdza tożsamość (kto?), autoryzacja uprawnienia (co wolno?).
  • Integracje wymagają odporności — timeout, retry, rate limit, idempotency i kolejka są ważniejsze niż sam fetch() do zewnętrznego API.
  • vs sesje — JWT jest bezstanowy i skalowalny, ale trudno go unieważnić przed wygaśnięciem; sesje dają pełną kontrolę kosztem przechowywania stanu.
  • Refresh token rotation — każde użycie wydaje nowy token; stary natychmiast traci ważność, co wykrywa kradzież.

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 . Źle obsłużony webhook potrafi podwoić zamówienie. Integracja bez retry i limitów zaczyna losowo psuć checkout. Niejasny model uwierzytelniania kończy się losowymi 401, problemami z cookie i debugowaniem, które wygląda jak szukanie błędu po omacku.

Uwaga

Najczęstszy błąd w tych obszarach to traktowanie uwierzytelniania, webhooków i real-time jako detali frontendowych. Każdy z nich dotyka zaufania, powtarzalności operacji i stanu po stronie serwera, więc decyzje trzeba podejmować na poziomie architektury.

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ą , 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.

Code
setInterval(async () => {
  const updates = await fetch('/api/notifications')
  // ...
}, 5000)

Long polling — request wisi otwarty, dopóki backend nie ma czego zwrócić:

Code
async function poll() {
  while (true) {
    const res = await fetch('/api/notifications/wait', { signal })
    if (res.ok) handleUpdate(await res.json())
  }
}

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.

Code
// Frontend
const events = new EventSource('/api/notifications/stream')
events.onmessage = (e) => {
  const data = JSON.parse(e.data)
  // ...
}
events.onerror = () => {
  // EventSource sam spróbuje się przepiąć
}
Code
// Backend (Express)
app.get('/api/notifications/stream', (req, res) => {
  res.setHeader('Content-Type', 'text/event-stream')
  res.setHeader('Cache-Control', 'no-cache')
  res.setHeader('Connection', 'keep-alive')
 
  const interval = setInterval(() => {
    res.write(`data: ${JSON.stringify({ time: Date.now() })}\n\n`)
  }, 1000)
 
  req.on('close', () => clearInterval(interval))
})

Idealne dla: powiadomień, feedów, AI streaming (ChatGPT-style), live progress.

Na co uważać: przy HTTP/1.x przeglądarki mają niski limit równoległych połączeń do jednej domeny, więc wiele otwartych kart z SSE może zablokować kolejne strumienie. Przy HTTP/2 limit jest negocjowany jako liczba strumieni, ale nadal zależy od konfiguracji klienta, serwera i proxy. Drugi praktyczny detal: natywny EventSource nie pozwala wygodnie dodać nagłówka Authorization, więc uwierzytelnienie zwykle rozwiązuje się przez cookie, podpisany URL albo krótkotrwały token w query stringu.

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.

Code
// Frontend
const ws = new WebSocket('wss://api.example.com/chat')
 
ws.onopen = () => ws.send(JSON.stringify({ type: 'join', room: 'general' }))
ws.onmessage = (e) => handleMessage(JSON.parse(e.data))
ws.onclose = () => reconnect()
Code
// Backend (ws library)
import { WebSocketServer } from 'ws'
 
const wss = new WebSocketServer({ port: 8080 })
 
wss.on('connection', (socket) => {
  socket.on('message', (data) => {
    wss.clients.forEach((client) => client.send(data))
  })
})

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.

ScenariuszRozwiązanie
Powiadomienia "nowy komentarz"SSE
Chat 1-on-1 / pokojeWebSockets
AI streaming (ChatGPT-like)SSE
Real-time graWebSockets
Status zadania w tleSSE / polling
Kolaboracja na dokumencie (CRDT)WebSockets

Popularne biblioteki i usługi: Socket.IO (abstrakcja nad WS z fallbackami), Pusher (usługa zarządzana), Ably, Soketi (samodzielnie hostowany odpowiednik Pushera), Cloudflare Durable Objects.

Wyzwania real-time

  • Skalowanie horyzontalne — drugi serwer nie wie o połączeniach pierwszego. Rozwiązanie: Redis pub/sub, NATS, Kafka albo zarządzany broker jako szyna komunikacyjna.
  • Autoryzacja kanałów — samo połączenie nie wystarczy. Backend musi sprawdzić, czy użytkownik ma prawo wejść do pokoju, subskrybować dokument albo dostać event konkretnej organizacji.
  • Sticky sessions — load balancer często musi trzymać klienta przy tym samym serwerze albo routing musi być świadomie zaprojektowany.
  • Reconnect logic — klient powinien wznowić od ostatniego eventu (Last-Event-ID w SSE), a backend powinien wiedzieć, czy potrafi odtworzyć pominięte wiadomości.
  • Heartbeat / ping — połączenie może wisieć jako „otwarte”, mimo że sieć realnie padła. Potrzebujesz heartbeatów i timeoutów po stronie klienta oraz serwera.
  • Backpressure — jeśli klient nie nadąża z odbiorem, serwer nie może bez końca buforować wiadomości w pamięci.
  • Proxy i — nie każda platforma dobrze wspiera długie połączenia. Zanim wybierzesz WebSockety, sprawdź limity hostingu, -a i load balancera.
Wskazówka

Real-time nie oznacza „każdy event natychmiast do każdego klienta”. Najpierw ustal, czy event musi być dostarczony z gwarancją, czy może zginąć, czy da się go odtworzyć po ponownym połączeniu i czy użytkownik ma prawo go zobaczyć.

2. Webhooks — backend powiadamia inny system

Webhook to odwrócone wywołanie . 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.

Code
Stripe → POST https://twoja-app.com/webhooks/stripe
Body: {
  "type": "payment_intent.succeeded",
  "data": { "amount": 10000, "currency": "pln" }
}

Trzy zasady webhooków

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:

Code
import Stripe from 'stripe'
 
app.post(
  '/webhooks/stripe',
  express.raw({ type: 'application/json' }), // raw body, nie JSON-parsed
  (req, res) => {
    const sig = req.headers['stripe-signature']
    let event
    try {
      event = stripe.webhooks.constructEvent(
        req.body,
        sig,
        process.env.STRIPE_WEBHOOK_SECRET,
      )
    } catch {
      return res.status(400).send('Invalid signature')
    }
    // przetwarzanie eventu
    res.json({ received: true })
  },
)

2. Odpowiadaj szybko

Webhook to nie miejsce na ciężką logikę. Dostawca zwykle oczekuje szybkiej odpowiedzi 2xx; timeout albo inny status spowoduje retry. Wzorzec: zweryfikuj podpis, zapisz event, wrzuć pracę do kolejki i odpowiedz 200 OK.

Code
app.post(
  '/webhooks/stripe',
  express.raw({ type: 'application/json' }),
  async (req, res) => {
    const event = verifyStripeSignature(req)
    await saveWebhookEvent(event.id, event.type, req.body)
    await queue.add('process-stripe-event', { eventId: event.id })
    res.status(200).end() // od razu
  },
)

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:

Code
async function handleWebhook(event, res) {
  const seen = await db.webhook.findUnique({ where: { eventId: event.id } })
  if (seen) return res.status(200).end()
 
  await db.webhook.create({ data: { eventId: event.id } })
  // ... właściwa logika
}

Bezpieczny flow webhooka

Najbezpieczniejszy schemat wygląda tak:

  1. Odbierz raw body, zanim parser JSON zmieni payload.
  2. Zweryfikuj podpis i świeżość timestampu.
  3. Zapisz event.id, typ, payload i status przetwarzania w bazie.
  4. Jeśli event.id już istnieje, odpowiedz 200 i nie wykonuj logiki drugi raz.
  5. Wrzuć ciężką pracę do kolejki.
  6. Odpowiedz szybko 2xx.
  7. Worker przetwarza event, aktualizuje status i zapisuje błąd, jeśli coś się nie uda.

To rozdziela dwa problemy: dostarczenie eventu i przetworzenie eventu. Dzięki temu retry dostawcy nie tworzy duplikatów, a awaria Twojej logiki biznesowej nie musi oznaczać utraty informacji o płatności.

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 3000 daje 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 webhooków

  • 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. Integracje zewnętrzne — gdy Twoja aplikacja woła cudze API

Webhooki są integracją przychodzącą. Druga połowa tematu to integracje wychodzące: Twoja aplikacja woła Stripe, Slacka, OpenAI, system ERP, bramkę SMS albo CRM. Dla frontendu często wygląda to jak jeden przycisk „Wyślij”, ale po stronie backendu trzeba obsłużyć wolne odpowiedzi, limity, retry i częściowe awarie.

Najważniejsza zasada: zewnętrzne API nie jest częścią Twojej aplikacji. Może zwolnić, zwrócić 429, zmienić komunikat błędu, mieć przerwę techniczną albo odpowiedzieć po czasie, gdy użytkownik już zamknął ekran.

Minimalny kontrakt integracji

Każda poważniejsza integracja powinna mieć:

  • timeout — request nie może wisieć w nieskończoność,
  • retry z exponential backoff — ponawiaj tylko błędy przejściowe, np. 408, 429, 5xx,
  • obsługę rate limitów — respektuj Retry-After, limity konta i limity endpointu,
  • idempotency key — szczególnie przy płatnościach, zamówieniach i operacjach tworzących zasoby,
  • mapowanie błędów — nie pokazuj użytkownikowi surowego błędu dostawcy,
  • logowanie request ID dostawcy — ułatwia support i debugowanie,
  • kolejkę — gdy operacja jest wolna, droga albo nie musi zakończyć się w tym samym requeście.

Przykład prostego retry z backoffem:

Code
async function callWithRetry(fn, maxAttempts = 3) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await fn()
    } catch (error) {
      const retryable = [408, 429, 500, 502, 503, 504].includes(error.status)
      if (!retryable || attempt === maxAttempts) throw error
 
      const delay = 2 ** attempt * 250
      await new Promise((resolve) => setTimeout(resolve, delay))
    }
  }
}

Kiedy kolejka zamiast request-response?

Jeżeli użytkownik musi natychmiast zobaczyć wynik, backend może poczekać na odpowiedź dostawcy. Jeżeli operacja jest ciężka albo podatna na awarie, lepiej zapisać zadanie i przetworzyć je asynchronicznie.

ScenariuszLepszy wzorzec
Sprawdzenie kuponu w checkoutrequest-response
Wysłanie e-maila po rejestracjikolejka
Import 20 000 rekordów z CRMkolejka + status postępu
Wystawienie faktury po płatnościwebhook + kolejka
Synchronizacja katalogu produktówcron / job cykliczny + retry
Powiadomienie UI o zakończeniu jobaSSE, WebSocket albo polling

Dead-letter queue i widoczność błędów

Retry nie może trwać bez końca. Po kilku nieudanych próbach zadanie powinno trafić do dead-letter queue albo statusu failed, który ktoś może przejrzeć i uruchomić ponownie ręcznie. Bez tego integracja „czasem nie działa”, ale nikt nie wie, które rekordy utknęły.

Dla frontendowca ważny jest też model stanu:

  • queued — zadanie przyjęte,
  • processing — backend pracuje,
  • succeeded — operacja zakończona,
  • failed_retryable — problem przejściowy, backend spróbuje ponownie,
  • failed_final — potrzebna akcja użytkownika lub supportu.

To pozwala zbudować UI, który nie udaje, że wszystko dzieje się synchronicznie.

4. Autentykacja i autoryzacja

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.

StatusCo oznaczaReakcja UI
401Nie wiadomo, kim jesteśSpróbuj odświeżyć sesję albo pokaż login
403Wiadomo, kim jesteś, ale nie masz dostępuPokaż brak uprawnień, nie przekierowuj na login
419 / 440Sesja wygasła lub CSRF token jest nieważnyOdśwież formularz, poproś o ponowne logowanie
429Za dużo prób logowania lub requestówPokaż cooldown i nie spamuj retry

Autentykacja — "kim jesteś?"

Autentykacja to weryfikacja tożsamości użytkownika.

Metody:

  • Hasło — tradycyjne logowanie
  • — "Zaloguj przez Google/GitHub"
  • Magic link — link logowania na e-mail
  • Passkeys — klucze FIDO/WebAuthn, zwykle odblokowywane biometrią, PIN-em albo kluczem sprzętowym

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.

Code
// Middleware autoryzacji
function requireAdmin(req, res, next) {
  if (req.user.role !== 'admin') {
    return res.status(403).json({ error: 'Forbidden' })
  }
  next()
}
 
app.delete('/api/users/:id', requireAdmin, deleteUser)

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.

Code
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.  ← Header
eyJ1c2VySWQiOjEyMywicm9sZSI6ImFkbWluIn0.  ← Payload
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c  ← Signature

Flow:

  1. Użytkownik loguje się → serwer tworzy krótko żyjący token
  2. Frontend trzyma go bezpiecznie (np. w pamięci) albo korzysta z mechanizmu ciasteczek (cookies)
  3. Przeglądarka wysyła token w nagłówku lub cookie
  4. Serwer weryfikuje podpis albo identyfikator sesji przy każdym request

Sessions

Alternatywa dla JWT — stan przechowywany na serwerze:

Code
1. Użytkownik loguje się → serwer tworzy sesję w bazie/Redis
2. Serwer zwraca session ID w cookie
3. Przeglądarka wysyła cookie automatycznie
4. Serwer sprawdza session ID w bazie

JWT vs sesje — kompromisy

CechaSesjaJWT
StanNa serwerze (Redis/DB)W tokenie (stateless)
WylogowanieNatychmiast (delete sesji)Trudne — token żyje do exp
SkalowanieWymaga wspólnego magazynu sesjiDobrze skaluje się bezstanowo
Rozmiar requestuMałe IDCały token w każdym requeście
Mobile / nativeOKCzęsto wybierane (brak cookies)
Cross-domainTrudneŁatwe

Reguła kciuka: monolit / web → sesje, API mikrousług / mobile → JWT (z krótkim TTL i refresh rotation).

W klasycznej aplikacji webowej sesja w HttpOnly cookie jest często prostsza operacyjnie niż własny system JWT. JWT ma sens, ale wymaga świadomego planu na krótki czas życia, rotację, unieważnianie, wylogowanie ze wszystkich urządzeń i wyciek tokena.

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.

Code
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=86400; Domain=.example.com
FlagCo robiPo co
HttpOnlyJS nie ma dostępu (document.cookie)Ochrona przed kradzieżą tokena przez XSS
SecureWysyłane tylko po HTTPSBrak transmisji w postaci jawnej
SameSite=StrictCookie tylko z requestów z tego samego sitePeł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 POSTDobry default
SameSite=NoneCookie idzie zawsze (wymaga Secure)Embedy iframe, third-party
PathTylko dla podanego pathOgraniczenie zasięgu
DomainSubdomeny mogą czytać.example.com = wszystkie subdomeny
Max-Age / ExpiresTTLPo wygaśnięciu cookie znika

CSRF — kuzyn XSS

Jeśli używasz cookies do uwierzytelniania, 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.

Code
<!-- evil.com -->
<form action="https://twojbank.pl/api/transfer" method="POST">
  <input name="to" value="atakujacy" />
  <input name="amount" value="10000" />
</form>
<script>
  document.forms[0].submit()
</script>

Przeglądarka sama dołączy cookie uwierzytelniające do tego POST-a, jeśli SameSite na to pozwala.

Obrona (warstwy):

  1. SameSite=Lax lub Strict na cookie uwierzytelniającym — chroni większość typowych scenariuszy automatycznie.
  2. 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).
  3. Sprawdzaj Origin / Referer — backend odrzuca requesty zmieniające stan, jeśli Origin nie 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ę. JWT albo sesja w HttpOnly cookie ogranicza kradzież tokena przez JavaScript, ale wymaga ochrony przed CSRF przez SameSite, token CSRF i kontrolę Origin.

Nie ma jednego „najlepszego” miejsca na token dla każdej aplikacji. Dla klasycznego weba najczęściej wygrywa cookie HttpOnly + Secure + SameSite=Lax/Strict. Dla korzystającej z OAuth coraz częściej spotkasz wariant: krótko żyjący access token w pamięci aplikacji i refresh token w bezpiecznym, HttpOnly cookie albo obsługę uwierzytelniania przez BFF.

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.

Code
1. Login → access (15 min) + refresh (7 dni)
2. Wywołanie API z access tokenem
3. 401 Unauthorized → frontend wywołuje POST /auth/refresh z refresh token
4. Backend zwraca nowy access + NOWY refresh (rotacja)
5. Stary refresh natychmiast unieważniony

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 użytkownika). Legalny użytkownik zostanie wylogowany — ale bezpiecznie.

Z perspektywy frontendu są tu trzy ważne reguły:

  1. Nie rób nieskończonej pętli refresh. Jeśli refresh zwróci 401, wyczyść stan i pokaż login.
  2. Zablokuj równoległe refreshe. Gdy pięć requestów naraz dostanie 401, tylko jeden powinien odświeżać token, a reszta poczekać na wynik.
  3. Rozróżniaj brak uwierzytelnienia od braku uprawnień. 401 może uruchomić refresh, ale 403 powinien pokazać brak dostępu.

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), klucze platformowe albo biometria (Touch ID, Face ID)
  • SMS — wygodne, ale podatne na SIM swap
  • Push notifications — Microsoft Authenticator, Duo

Passkeys są oparte o kryptografię klucza publicznego: serwer przechowuje klucz publiczny, a klucz prywatny zostaje na urządzeniu albo w zaufanym menedżerze poświadczeń. Dzięki temu serwer nie zna sekretu, który da się wyłudzić phishingiem tak łatwo jak hasło. W praktyce nadal trzeba jednak przewidzieć recovery, zmianę urządzenia i użytkowników bez kompatybilnego środowiska.

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 — usługa zarządzana, świetne , droga przy skali,
  • Supabase Auth — w pakiecie z bazą,
  • Auth0 / Okta — enterprise,
  • Lucia — obecnie bardziej zasób edukacyjny do implementowania sesji niż biblioteka do nowych wdrożeń produkcyjnych,
  • Better Auth — nowoczesne, framework-agnostic.
Wskazówka

Auth warto kupić albo oprzeć o dojrzałą bibliotekę, jeśli nie masz mocnego powodu, żeby pisać go samodzielnie. Własny login to nie tylko formularz: reset hasła, MFA, blokady po wielu próbach, sesje, audyt, recovery i bezpieczne wylogowanie z urządzeń.

Werdykt Labu

Auth, real-time, webhooki i integracje łączy wspólny mianownik, ponieważ żaden z tych obszarów nie dotyczy tylko przesyłania danych, ale przede wszystkim zaufania, powtarzalności i stanu. To sprawia, że wybór między a WebSockets staje się decyzją o całym modelu komunikacji, webhook bez weryfikacji podpisu jest jak zostawienie otwartych drzwi, a integracja bez retry i idempotency potrafi zepsuć proces zakupowy mimo poprawnego UI.

Frontendowiec, który rozumie te mechanizmy, przestaje debugować po omacku i zaczyna projektować UI z realistycznym modelem stanów — zalogowany, token wygasł, brak uprawnień, zadanie w kolejce, integracja chwilowo niedostępna, sieć padła. To robi większą różnicę niż znajomość kolejnej biblioteki komponentów.

Połączenie intuicyjności z wydajnością, które zapewnia bezproblemową skalowalność kodu.
React

Pozostałe części serii

  • Backend dla frontendowca: serwer, bazy danych i API
  • Backend dla frontendowca: cache, deployment i bezpieczeństwo
  • 1. Real-time — WebSocket, SSE, polling3 min
  • 2. Webhooks — backend powiadamia inny system2 min
  • 3. Integracje zewnętrzne — gdy Twoja aplikacja woła cudze API2 min
  • 4. Autentykacja i autoryzacja6 min
  • Werdykt Labu1 min
  • Pozostałe części serii1 min

Często zadawane pytania

Źródła i dokumentacjaZweryfikowano: 11 czerwca 2026

Materiały wykorzystane do weryfikacji artykułu „Backend dla frontendowca: auth, real-time i integracje”:

MDN: Using server-sent events, MDN: WebSockets API, MDN: HTTP cookies, OWASP Authentication Cheat Sheet, OWASP Cross-Site Request Forgery Prevention, OWASP JSON Web Token Cheat Sheet, Stripe: Webhooks, Lucia v3: Migrate, FIDO Alliance: Passkeys.

Seria

Backend dla frontendowca
Część 2 / 3
  1. 1Backend dla frontendowca: serwer, bazy danych i API
  2. Backend dla frontendowca: auth, real-time i integracje
  3. 3Backend dla frontendowca: cache, deployment i bezpieczeństwo
Maciej Sala

O autorze

Maciej Sala

Maciej Sala — Product Manager i Frontend Developer z bogatym doświadczeniem w marketingu internetowym oraz SEO. Na co dzień pracuje z Reactem, Next.js i TypeScriptem, a ostatnio także z Astro i narzędziami do automatyzacji procesów AI. Sprawnie łączy perspektywę produktową z praktycznym podejściem do kodu. Przez kilka lat był związany z branżą gier wideo jako project manager i game designer. Absolwent historii na Uniwersytecie Jagiellońskim oraz studiów podyplomowych z marketingu internetowego na AGH w Krakowie. Po godzinach trenuje na siłowni, maluje figurki i rozwija własne projekty side-projecty.

Moje artykułyWięcej o mnie

Pomagam przekładać takie tematy na konkretne wdrożenia w frontendzie, SEO, analityce i procesie produktowym.

Skontaktuj się ze mną

Biblioteka wiedzy

Czytaj dalej

Zobacz więcej wpisów
Backend dla frontendowca: cache, deployment i bezpieczeństwo
Backend dla frontendowca: cache, deployment i bezpieczeństwo

Redis, cache HTTP, kolejki, deployment, monitoring, OWASP Top 10:2025 i RODO — backendowa wiedza, którą frontendowiec powinien znać.

Maciej Sala

Maciej Sala

Founder Strivelab

30 lipca 2025
Backend dla frontendowca: serwer, bazy danych i API
Backend dla frontendowca: serwer, bazy danych i API

Pierwsza część serii Backend dla frontendowca: architektura aplikacji, serwer, bazy danych, API, statusy HTTP, paginacja, idempotency, BFF i CORS.

Maciej Sala

Maciej Sala

Founder Strivelab

28 lipca 2025
REST API — zasady projektowania i dobre praktyki
REST API — zasady projektowania i dobre praktyki

REST API zaprojektowane naprędce wróci do Ciebie z długiem. Konwencje, wersjonowanie i obsługa błędów — zasady, których tutoriale zwykle pomijają.

Maciej Sala

Maciej Sala

Founder Strivelab

5 grudnia 2025

Poprzedni wpis

Backend dla frontendowca: serwer, bazy danych i API
Backend dla frontendowca: serwer, bazy danych i API

Pierwsza część serii Backend dla frontendowca: architektura aplikacji, serwer, bazy danych, API, statusy HTTP, paginacja, idempotency, BFF i CORS.

Maciej Sala

Maciej Sala

Founder Strivelab

28 lipca 2025

Następny wpis

Backend dla frontendowca: cache, deployment i bezpieczeństwo
Backend dla frontendowca: cache, deployment i bezpieczeństwo

Redis, cache HTTP, kolejki, deployment, monitoring, OWASP Top 10:2025 i RODO — backendowa wiedza, którą frontendowiec powinien znać.

Maciej Sala

Maciej Sala

Founder Strivelab

30 lipca 2025