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, 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
25 maja 2026 10:55

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.

Artykuł 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, zapisuj idempotentnie, odpowiadaj 200 natychmiast i 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ż

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.

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 (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 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 autoryzacji, który pozwala aplikacji uzyskać ograniczony dostęp do konta użytkownika bez przejmowania jego 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 — minimalna, type-safe, hostowana samodzielnie
  • Better Auth — nowoczesne, framework-agnostic

Werdykt Labu

Auth, real-time i webhooki mają wspólny mianownik: każdy z tych obszarów dotyczy zaufania i stanu, a nie tylko przesyłania danych. SSE i WebSockets to nie wybór biblioteki — to decyzja o modelu komunikacji. Webhook bez weryfikacji podpisu to otwarte drzwi. JWT bez refresh rotation to kompromis między wygodą a bezpieczeństwem, który trzeba świadomie podjąć.

Frontendowiec, który rozumie te mechanizmy, przestaje debugować 401 po omacku i zaczyna projektować UI z realistycznym modelem stanów — zalogowany, token wygasł, brak uprawnień, 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, polling2 min
  • 2. Webhooks — backend powiadamia inny system1 min
  • 3. Autoryzacja i autentykacja4 min
  • Werdykt Labu1 min
  • Pozostałe części serii1 min

Często zadawane pytania

Źródła i dokumentacjaZweryfikowano: 13 maja 2026

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

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

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, OWASP i RODO — część backendowej wiedzy, której frontendowiec unika jak ognia. Dlaczego warto jednak ją mieć i od czego zacząć?

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

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 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ństwoRedis, cache HTTP, OWASP i RODO — część backendowej wiedzy, której frontendowiec unika jak ognia. Dlaczego warto jednak ją mieć i od czego zacząć?
Maciej Sala

Maciej Sala

Founder Strivelab

30 lipca 2025