StriveLab
Strony internetowe
Usługi
RealizacjeO mnieBlogPorozmawiajmy
PL
EN
StriveLab
Strony internetowe
Usługi
RealizacjeO mnieBlogPorozmawiajmy
PL
EN

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.

Astro

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

Doradztwo produktowe

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

QA & Automation

Testy automatyczne komponentów i E2E w oparciu o Cypress.

SEO & Performance

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

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
  • SEO & Performance Sprint
  • QA & Stabilizacja
  • Konsultacje Product / Delivery
  • 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, sesje, JWT, cookies, CSRF, refresh token rotation i MFA.

OpublikujLinkedInFacebookWyślij
Autor
Maciej Sala
Opublikowano
29 lipca 2025 11:45
Czytanie
8 min czytania
Aktualizacja
1 maja 2026 16:30

W skrócie

  • SSE zamiast WebSocket — dla komunikacji serwer→klient SSE jest prostszy, działa przez HTTP/2 i nie wymaga osobnego serwera WebSocket
  • WebSocket = dwukierunkowy — wybierz gdy klient też musi wysyłać wiadomości (chat, multiplayer), nie dla prostych powiadomień
  • Trzy zasady webhooków — weryfikuj sygnaturę HMAC, odpowiadaj 200 natychmiast, logikę przetwarzaj asynchronicznie w kolejce
  • Autentykacja ≠ autoryzacja — autentykacja sprawdza tożsamość (kto?), autoryzacja uprawnienia (co wolno?)
  • JWT vs sesje — JWT jest bezstanowy i skalowalny, ale nie można go unieważnić przed wygaśnięciem; sesje dają pełną kontrolę kosztem storage
  • Refresh token rotation — każde użycie wydaje nowy token; stary natychmiast traci ważność, co wykrywa kradzież

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.

Uwaga

Najczęstszy błąd w tych obszarach to traktowanie auth, webhooków i realtime 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ą 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.

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.

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: 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-ID w 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.

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

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:

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 (< 2s)

Webhook to nie miejsce na ciężką logikę. Dostawca ma timeout (Stripe: 30s, ale prowadzi to do retry). Wzorzec: enqueue + 200 OK:

Code
app.post('/webhooks/stripe', async (req, res) => {
  await queue.add('process-stripe-event', { event })
  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
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

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

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. User 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. User 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
SkalowanieTrzeba współdzielić storageIdealnie skaluje się stateless
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).

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 plaintext
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 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.

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 auth do tego POST-a, jeśli SameSite na to pozwala.

Obrona (warstwy):

  1. SameSite=Lax lub Strict na cookie auth — chroni 95% przypadków 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ę. 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.

Code
1. Login → access (15 min) + refresh (7 dni)
2. API call 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 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

Źródła i dokumentacja

  • MDN: Server-sent events
  • MDN: WebSockets API
  • MDN: HTTP cookies
  • OWASP Authentication Cheat Sheet
  • OWASP Cross-Site Request Forgery Prevention

Często zadawane pytania

SSE wybierz dla jednokierunkowego strumienia serwer → klient: powiadomienia, AI streaming, live progress i feedy. Działa na zwykłym HTTP, ma automatyczny reconnect i jest prostsze w utrzymaniu. WebSockets wybierz dla komunikacji dwukierunkowej: chatów, gier, współpracy na dokumencie i scenariuszy, w których klient też stale wysyła zdarzenia do serwera.

Pracuję z tym zawodowo.

Jeśli chcesz przełożyć ten temat na lepszą architekturę frontendu, uporządkować React lub Next.js i podnieść jakość pracy zespołu, skontaktuj się ze mną. Pomagam zamieniać wiedzę z artykułów w praktyczne decyzje technologiczne.

Skontaktuj się ze mną
Maciej Sala

O autorze

Maciej Sala

Maciej Sala — project manager i frontendowiec z doświadczeniem w marketingu internetowym. Na co dzień pracuję z Reactem, Next.js i TypeScriptem, łącząc perspektywę produktową z praktycznym podejściem do kodu. Przez kilka lat związany z branżą gier wideo jako project manager i game designer.

Absolwent historii na Uniwersytecie Jagiellońskim i studiów podyplomowych z marketingu internetowego na Akademii Górniczo-Hutniczej w Krakowie. Poza pracą trenuje na siłowni, maluje figurki i realizuje własne projekty.

Moje artykułyWięcej o mnie

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
Poprzedni wpisBackend dla frontendowca: serwer, bazy danych i APIPierwsza część serii Backend dla frontendowca: architektura aplikacji, serwer, bazy danych, API, status code, paginacja, idempotency, BFF i CORS.
Maciej Sala

Maciej Sala

Founder Strivelab

28 lipca 2025
Następny wpisBackend dla frontendowca: cache, deployment i bezpieczeństwoTrzecia część serii Backend dla frontendowca: Redis, HTTP cache, kolejki, file storage, deployment, CI/CD, monitoring, OWASP, rate limiting i RODO.
Maciej Sala

Maciej Sala

Founder Strivelab

30 lipca 2025

Spis treści

5 sekcji · 9 min

  • 1. Real-time — WebSocket, SSE, polling2 min
  • 2. Webhooks — backend powiadamia inny system1 min
  • 3. Autoryzacja i autentykacja4 min
  • Co dalej w serii?1 min
  • Źródła i dokumentacja1 min

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

Trzecia część serii Backend dla frontendowca: Redis, HTTP cache, kolejki, file storage, deployment, CI/CD, monitoring, OWASP, rate limiting i RODO.

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, status code, 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

Praktyczny przewodnik po projektowaniu REST API. Konwencje URL, metody HTTP, błędy, wersjonowanie, paginacja i kilka ważnych niuansów, które zwykle pomija się w prostych tutorialach.

Maciej Sala

Maciej Sala

Founder Strivelab

5 grudnia 2025