Token CSRF w Django i Flasku - praktyczna ochrona formularzy

Konstanty Jankowski • 15 października 2026
Programista Django walczy z atakiem, gdy token CSRF chroni przed hakerem.

Spis treści

Formularz zmiany hasła, przelew albo usunięcie wpisu mogą wyglądać zupełnie normalnie, a mimo to zostać uruchomione przez obcą stronę w imieniu zalogowanego użytkownika. Token CSRF pomaga przerwać ten scenariusz, sprawdzając, czy żądanie rzeczywiście pochodzi z naszej aplikacji. Pokażę, jak działa ten mechanizm, kiedy jest potrzebny, jak wdrożyć go w Django i Flasku oraz dlaczego samo ustawienie ciasteczka SameSite nie zawsze wystarcza.

Najważniejsze zasady ochrony formularzy i API

  • CSRF wykorzystuje automatyczne wysyłanie ciasteczek sesyjnych przez przeglądarkę.
  • Token musi być losowy, tajny i sprawdzany po stronie serwera przy każdej operacji zmieniającej dane.
  • Metody GET nie powinny zmieniać stanu aplikacji, dlatego zwykle chroni się POST, PUT, PATCH i DELETE.
  • SameSite jest dodatkową warstwą ochrony, ale nie powinno zastępować tokenów w aplikacjach opartych na sesjach.
  • Django i Flask-WTF mają gotowe mechanizmy CSRF, więc własna implementacja najczęściej nie ma sensu.

Schemat ilustruje, jak zły aktor próbuje obejść zabezpieczenia, np. token CSRF, atakując przez proxy.

Na czym polega atak CSRF

Atak Cross-Site Request Forgery polega na nakłonieniu przeglądarki zalogowanej osoby do wysłania żądania do zaufanej aplikacji. Przeglądarka dołącza do takiego żądania ciasteczko sesyjne, więc serwer może uznać je za działanie użytkownika, nawet jeśli kliknięcie zostało wywołane przez zupełnie inną stronę.

Wyobraźmy sobie panel sklepu pod adresem sklep.example. Użytkownik jest zalogowany, a jego sesja pozwala zmienić adres dostawy. Napastnik może umieścić na swojej stronie formularz kierujący żądanie do sklepu i spróbować nakłonić użytkownika do jego wysłania, na przykład przez pozornie niewinny przycisk.

Jeżeli aplikacja sprawdza wyłącznie ważność sesji, żądanie może zostać zaakceptowane. Serwer widzi zalogowanego użytkownika, ale nie wie, czy to on zainicjował operację. Właśnie tę lukę uzupełnia dodatkowy sekret przekazywany w formularzu albo nagłówku HTTP.

Skutki zależą od uprawnień ofiary. CSRF może prowadzić do zmiany adresu e-mail, podmiany numeru rachunku, usunięcia danych, utworzenia użytkownika administracyjnego albo wykonania zakupu. Jak podaje OWASP, zabezpieczenie jest szczególnie istotne w aplikacjach, które uwierzytelniają użytkowników za pomocą ciasteczek.

Jak działa token CSRF w praktyce

Mechanizm jest prosty. Serwer generuje nieprzewidywalną wartość i wiąże ją z sesją albo użytkownikiem. Aplikacja umieszcza ją w formularzu HTML lub udostępnia skryptowi JavaScript, a przy żądaniu zmieniającym dane klient odsyła ją z powrotem.

Serwer porównuje token z żądania z wartością oczekiwaną. Brak tokena, jego wygaśnięcie albo niezgodność kończą się odrzuceniem operacji, zwykle kodem 403 Forbidden albo 400 Bad Request. Napastnik może znać adres endpointu, ale nie powinien móc odczytać poprawnego tokena z obcej domeny.

Najpopularniejsze warianty

Wariant Jak działa Kiedy pasuje
Synchronizowany z sesją Token jest przechowywany po stronie serwera i porównywany z wartością w żądaniu. Klasyczne aplikacje renderujące formularze po stronie serwera.
Double submit cookie Ta sama wartość trafia do ciasteczka i do formularza lub nagłówka, a serwer porównuje oba miejsca. Aplikacje SPA i API korzystające z ciasteczek.
Cookie-to-header JavaScript odczytuje ciasteczko z tokenem i wysyła je w niestandardowym nagłówku. Frontend wykorzystujący fetch, Axios albo podobne biblioteki.

W praktyce nie przywiązuję dużej wagi do tego, czy token zmienia się przy każdym żądaniu, czy raz na sesję. Najważniejsze są losowość, poprawne powiązanie z sesją i konsekwentna walidacja. Token jednorazowy może zwiększyć odporność na ponowne użycie, ale bywa kłopotliwy dla użytkownika korzystającego z kilku kart, przycisku Wstecz albo równoległych formularzy.

Czego token nie zapewnia

Token CSRF nie chroni przed przejęciem sesji, kradzieżą haseł ani podatnością XSS. Jeżeli atakujący może uruchomić własny JavaScript w obrębie zaufanej aplikacji, często może odczytać token i użyć go zgodnie z przeznaczeniem. Dlatego zabezpieczenie CSRF trzeba łączyć z ochroną przed XSS, bezpiecznym kodowaniem danych wyjściowych i właściwą polityką Content Security Policy.

Co chronić i jak nie popełnić podstawowego błędu

Ochrony wymagają wszystkie żądania, które zmieniają stan aplikacji. Dotyczy to nie tylko formularzy logowania, ale również operacji takich jak edycja profilu, zmiana hasła, usunięcie rekordu, dodanie produktu czy wysłanie komentarza.

  • POST powinien być chroniony, gdy zapisuje lub usuwa dane.
  • PUT i PATCH wymagają ochrony przy aktualizacji zasobów.
  • DELETE powinien mieć zabezpieczenie przed nieautoryzowanym usunięciem.
  • GET, HEAD i OPTIONS powinny być bezpieczne i nie powodować zmian w bazie.

Jednym z częstszych błędów jest endpoint w rodzaju /delete-user?id=15, który usuwa dane po zwykłym wejściu w adres. To zły projekt niezależnie od CSRF, bo link, obrazek albo przekierowanie mogą uruchomić operację bez wyraźnej zgody użytkownika. Metody odczytujące dane powinny pozostać odczytujące, a działania modyfikujące trzeba wyraźnie oddzielić.

Nie należy też wyłączać ochrony globalnie tylko dlatego, że jeden endpoint zgłasza błąd. Lepsze rozwiązanie to sprawdzenie, czy token został dodany do formularza, czy frontend wysyła właściwy nagłówek i czy domeny oraz protokoły są skonfigurowane zgodnie z aplikacją.

SameSite, Secure i HttpOnly

Atrybut SameSite ogranicza wysyłanie ciasteczek w żądaniach między witrynami. Warto ustawić SameSite=Lax albo, jeśli aplikacja na to pozwala, SameSite=Strict. To dobra warstwa obrony, lecz nie pełny zamiennik tokena, zwłaszcza gdy aplikacja obsługuje różne domeny, integracje lub nietypowe przepływy logowania.

Ciasteczko sesyjne powinno zwykle mieć również Secure, aby było wysyłane wyłącznie przez HTTPS, oraz HttpOnly, aby JavaScript nie mógł go odczytać. Ciasteczko przeznaczone do odczytu przez frontend, zawierające sam token CSRF, może wymagać braku HttpOnly. Nie wolno jednak mylić go z ciasteczkiem sesyjnym.

Atrybut Rola Typowe znaczenie
Secure Ogranicza wysyłanie ciasteczka do HTTPS. Standard dla sesji produkcyjnych.
HttpOnly Blokuje dostęp do ciasteczka z JavaScriptu. Wskazany dla identyfikatora sesji.
SameSite=Lax Ogranicza wysyłanie ciasteczka między witrynami. Rozsądny domyślny kompromis.
SameSite=Strict Zapewnia bardziej rygorystyczne ograniczenie. Dobre rozwiązanie, jeśli nie psuje logowania i integracji.

Wdrożenie w Django i Flasku

W ekosystemie Pythona nie zaczynałbym od pisania własnego generatora tokenów. Frameworki mają gotowe rozwiązania, które obsługują generowanie, przechowywanie i walidację. Dokumentacja Django zaleca użycie middleware CSRF oraz znacznika {% csrf_token %} w formularzach POST.

Django

W typowym projekcie trzeba pozostawić aktywny middleware CsrfViewMiddleware, a w formularzu dodać ukryte pole z tokenem.

{% csrf_token %}

Dla żądań JavaScript token można pobrać z ciasteczka csrftoken albo umieścić go w dokumencie HTML i przekazać w nagłówku X-CSRFToken. Przy aplikacji korzystającej z osobnego frontendu trzeba dodatkowo sprawdzić konfigurację CORS, domenę ciasteczka i ustawienia zaufanych źródeł.

Przeczytaj również: SSR - Czy na pewno wiesz, jak przyspieszyć swoją stronę?

Flask z Flask-WTF

W aplikacji Flask wygodnym rozwiązaniem jest rozszerzenie Flask-WTF. Ochronę można włączyć globalnie, a formularz otrzyma pole CSRF automatycznie, jeśli korzysta z FlaskForm.

from flask import Flask
from flask_wtf.csrf import CSRFProtect

app = Flask(__name__)
app.config["SECRET_KEY"] = "losowy-sekret-zmieniony-w-produkcji"

csrf = CSRFProtect(app)

Własny formularz powinien zawierać pole z wartością wygenerowaną przez framework.

Dla żądań AJAX token można wysłać w nagłówku.

const token = document.querySelector(
  'meta[name="csrf-token"]'
).content;

fetch("/api/profile", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-CSRFToken": token
  },
  body: JSON.stringify({ display_name: "Ala" })
});

Nie wyłączałbym CSRF dla całego API tylko dlatego, że część endpointów jest używana przez aplikację mobilną. Wyjątek powinien być wąski i uzasadniony, na przykład dla webhooka uwierzytelnianego podpisem HMAC albo API używającego tokena w nagłówku Authorization zamiast ciasteczka sesyjnego.

CSRF w aplikacjach SPA i API

W aplikacji jednostronicowej problem często pojawia się na styku frontendu i backendu. Jeżeli backend uwierzytelnia użytkownika przez ciasteczko, przeglądarka może wysyłać je automatycznie. Sam fakt, że frontend korzysta z fetch, nie oznacza jeszcze ochrony.

Praktyczny schemat wygląda tak. Backend udostępnia token w ciasteczku przeznaczonym do odczytu przez JavaScript albo w kodzie strony. Frontend pobiera wartość i dodaje ją do niestandardowego nagłówka przy żądaniach POST, PUT, PATCH i DELETE. Backend sprawdza zarówno sesję, jak i token.

Jeżeli zamiast ciasteczek używamy tokena dostępowego przekazywanego ręcznie w nagłówku Authorization, klasyczny CSRF zwykle nie jest głównym problemem, bo przeglądarka nie dołącza takiego nagłówka automatycznie do żądania z obcej strony. Nadal trzeba jednak chronić token dostępowy przed XSS, zadbać o CORS i poprawnie obsłużyć odświeżanie sesji.

CORS także nie jest prostym zamiennikiem CSRF. Błędna konfiguracja z szerokim Access-Control-Allow-Origin i włączonym Access-Control-Allow-Credentials może umożliwić obcej stronie wykonywanie żądań z poświadczeniami. Dozwolone originy powinny być jawnie określone, bez bezrefleksyjnego używania gwiazdki.

Jak sprawdzić, czy ochrona działa

Test zaczynam od sprawdzenia, czy operacja zmieniająca dane kończy się błędem po usunięciu tokena. Następnie wysyłam token nieprawidłowy, przeterminowany i należący do innej sesji. W każdym przypadku backend powinien odrzucić żądanie, a log nie powinien ujawniać samej wartości sekretu.

  1. Otwórz formularz w przeglądarce i sprawdź, czy zawiera ukryte pole CSRF.
  2. Wyślij formularz bez tego pola i potwierdź odpowiedź błędu.
  3. Zmień pojedynczy znak tokena i sprawdź, czy żądanie nadal jest blokowane.
  4. Przetestuj żądania JavaScript w zakładce Network.
  5. Zweryfikuj atrybuty ciasteczek, szczególnie Secure, HttpOnly i SameSite.
  6. Sprawdź, czy endpointy GET nie wykonują operacji zapisu ani usuwania.

W testach automatycznych warto mieć osobne przypadki dla braku nagłówka, błędnej wartości, poprawnego tokena oraz wygasłej sesji. Przydatne jest też logowanie typu błędu i adresu endpointu, ale nie należy zapisywać tokenów ani ciasteczek sesyjnych. Sam komunikat dla użytkownika powinien być krótki, natomiast szczegóły mogą trafić do bezpiecznego systemu monitoringu.

Co naprawdę robi różnicę przy ochronie przed CSRF

Dobrze skonfigurowany framework, poprawny podział metod HTTP i bezpieczne ciasteczka dają znacznie więcej niż własny, przypadkowy mechanizm oparty na stałej wartości w kodzie. Token powinien być generowany kryptograficznie, przypisany do właściwej sesji i sprawdzany przed wykonaniem operacji.

Największym praktycznym ryzykiem nie jest zwykle brak wiedzy o samym CSRF, lecz pojedynczy wyjątek dodany „na chwilę”, endpoint zmieniający dane przez GET albo błędnie skonfigurowane CORS. Dlatego przy przeglądzie aplikacji sprawdzam nie tylko formularze, ale całą powierzchnię API i wszystkie miejsca, w których ciasteczko sesyjne daje dostęp do operacji zapisu.

Jeśli aplikacja korzysta z Django lub Flask-WTF, rozsądny plan jest krótki. Włącz ochronę globalnie, dodaj token do formularzy, skonfiguruj nagłówki dla JavaScriptu, ustaw bezpieczne ciasteczka i przetestuj odrzucanie błędnych żądań. To niewielki koszt, a różnica między „użytkownik był zalogowany” a „użytkownik faktycznie zatwierdził operację” ma w bezpieczeństwie ogromne znaczenie.

FAQ - Najczęstsze pytania

Obca strona nakłania przeglądarkę zalogowanego użytkownika do wysłania żądania do zaufanej aplikacji. Przeglądarka automatycznie dołącza ciasteczko sesyjne, dlatego serwer może uznać operację za działanie użytkownika. Token CSRF dodaje sekret, który musi zostać przekazany i poprawnie zweryfikowany przez serwer.

Token należy sprawdzać przy operacjach zmieniających dane, czyli zwykle POST, PUT, PATCH i DELETE. Metody GET, HEAD i OPTIONS powinny być bezpieczne i nie modyfikować stanu aplikacji. Usuwanie danych przez adres w rodzaju /delete-user?id=15 jest błędnym projektem niezależnie od zastosowania tokena.

SameSite=Lax lub SameSite=Strict ogranicza wysyłanie ciasteczek między witrynami, ale nie powinno zastępować tokenów w aplikacjach opartych na sesjach. Token warto połączyć z ciasteczkami Secure i HttpOnly dla sesji oraz właściwą konfiguracją domen i integracji. Ciasteczko z tokenem przeznaczonym do odczytu przez JavaScript może wymagać wyłączenia HttpOnly.

W Django należy pozostawić aktywny middleware CsrfViewMiddleware i dodać {% csrf_token %} do formularzy POST. W Flasku można użyć Flask-WTF i CSRFProtect, a formularz oparty na FlaskForm otrzyma ochronę automatycznie. Żądania JavaScript powinny przekazywać token w nagłówku X-CSRFToken.

Jeżeli token dostępowy jest przekazywany ręcznie w nagłówku Authorization, klasyczny CSRF zwykle nie jest głównym problemem, ponieważ przeglądarka nie dodaje takiego nagłówka automatycznie z obcej strony. Nadal trzeba chronić token przed XSS, poprawnie skonfigurować CORS i obsłużyć odświeżanie sesji. API uwierzytelniane ciasteczkiem nadal wymaga sprawdzania tokena CSRF.

Oceń artykuł

Ocena: 0.00 Liczba głosów: 0

Tagi

cors
csrf
django
flask
samesite
Autor Konstanty Jankowski
Konstanty Jankowski
Nazywam się Konstanty Jankowski i od sześciu lat zajmuję się programowaniem, szczególnie w języku Python oraz nowoczesnymi technologiami. Moje zainteresowanie tymi tematami zrodziło się podczas studiów, kiedy odkryłem, jak wiele możliwości stwarza programowanie w codziennym życiu. Lubię dzielić się swoją wiedzą i pomagać innym zrozumieć złożoność zagadnień związanych z technologią. W moich tekstach skupiam się na praktycznych aspektach programowania, analizując aktualne trendy oraz uproszczając trudne koncepcje, aby były dostępne dla każdego. Dokładam wszelkich starań, aby dostarczać rzetelne, zrozumiałe i aktualne informacje. Staram się porównywać różne źródła i organizować wiedzę w sposób jasny, co pozwala mi na skuteczne przekazywanie informacji. Wierzę, że dobra edukacja w obszarze programowania i technologii może otworzyć drzwi do wielu fascynujących możliwości.

Udostępnij artykuł

Napisz komentarz