Jak konfigurować atrybut SameSite w ciasteczkach?
January 27, 2020 ‐ 8 minut(a) czytania
W poprzedniej części serii poświęconej zmianom w obsłudze ciasteczek pokazałem, o co chodzi w SameSite w ciasteczkach, skrótowo wyjaśniając co to są ciasteczka, jak konfigurowane i instalowane są pliki cookies oraz jak są obsługiwane przez przeglądarki.
Dzisiaj chcę pokazać, w jaki sposób przeglądarki oparte na Chromium implementują wytyczne RFC6265bis oraz podzielić się przepisami na „udane ciasteczka”.
Zacznijmy od tego, że zmiany pojawią się w stabilnych wydaniach najpopularniejszych przeglądarek już za kilka dni. W Google Chrome w wersji 80 zaplanowanej na 4 lutego 2020. Microsoft Edge około tygodnia później (15 stycznia udostępniono pierwszą wersję stabilną, nie ma jeszcze oficjalnego terminarza wydań, ale dotychczasowa praktyka pokazuje przynajmniej kilkudniowe opóźnienie względem Chromium). W Firefoksie domyślne ustawienia raczej nie zostaną zmienione wcześniej niż wraz z wydaniem Firefox ESR 68.6, a więc 6 marca.
Zmiany dotyczące sposobu obsługi ciasteczek zostały opisane w dwóch artykułach:
- Cookies default to SameSite=Lax
Treat cookies as SameSite=Lax by default if no SameSite attribute is specified. Developers are still able to opt-in to the status quo of unrestricted use by explicitly asserting SameSite=None.
Note: Chrome will make an exception for cookies set without a SameSite attribute less than 2 minutes ago. Such cookies will also be sent with non-idempotent (e.g. POST) top-level cross-site requests despite normal SameSite=Lax cookies requiring top-level cross-site requests to have a safe (e.g. GET) HTTP method. Support for this intervention (“Lax + POST”) will be removed in the future. - Reject insecure SameSite=None cookies
Deprecate and remove the use of cookies with the SameSite=None attribute but without the Secure attribute. Any cookie that requests SameSite=None but is not marked Secure will be rejected.
Ciasteczka bez atrybutu SameSite będą domyślnie traktowane jak te z wartością Lax. Co oznacza, że domyślnym zachowaniem będzie ograniczenie plików cookie tylko do własnych kontekstów. W okresie przejściowym (zanim nowe wymagania zostaną powszechnie zaadoptowane) będzie istniała obsługa opisana jako Lax + POST, dzięki której ciasteczka bez atrybutu SameSite przez 2 minuty od wystawienia będą załączane do nieidemopotentnych żądań między domenami najwyższego poziomu.
Pliki cookie do użytku w różnych witrynach muszą zawierać SameSite=None; Secure, aby umożliwić włączenie w kontekście strony trzeciej.
Scenariusze użycia ciasteczek zewnętrznych lub pomiędzy stronami
Istnieje wiele typowych scenariuszy użycia i wzorców, w których pliki cookie muszą być wysyłane w kontekście strony trzeciej. Jeśli stosujecie takie rozwiązania, upewnijcie się, że wydawane lub przekazywane ciasteczka będą na czas zaktualizowane, aby zapewnić prawidłowe działanie usług.
Zawartość pływających ramek <iframe>
Zawartość z innych stron, wyświetlana w pływających ramkach <iframe>
wyświetlana jest w kontekście zewnętrznym (third-party).
Najczęściej
są to następujące przypadki użycia:
- Zagnieżdżenie współdzielonych z innych stron materiałów, takich jak wideo, mapy, fragmenty kodu źródłowego oraz wpisy z mediów społecznościowych.
- Widżety z zewnętrznych usług, takich jak systemy mikropłatności, panele dyskusyjne i komentarze, kalendarze lub systemy rezerwacyjne.
- Widżety w postaci przycisków do polecania lub systemy do ochrony przed oszustwami finansowymi.
Ciasteczka mogą być w tych przykładach wykorzystane do, między innymi, utrzymania stanu sesji, przechowywania ogólnych preferencji, prowadzenia statystyk lub personalizowania treści dla użytkowników posiadających istniejące konta.
Dodatkowo, treści w ramach typu <iframes>
są osadzane także w kontekście stron najwyższego poziomy lub własnym (first-party). Wszystkie ciasteczka dostarczane przez strony w ramkach będą traktowane jako zewnętrzne. Jeśli tworzycie strony, które z założenia mają być zagnieżdżane w innych, a ich
funkcjonalność
jest zależna od ciasteczek, musicie upewnić się, że są one skonfigurowane do użycia pomiędzy stronami (cross-site) lub opracować
mechanizmy
zastępcze, które pozwolą na obsługę zawartości bez ciasteczka załączonego w żądaniu.
„Niebezpieczne” żądania pomiędzy stronami
Określenie
„niebezpieczne”
budzi pewne obawy, ale odnosi się ono do każdego żądania, które może mieć na celu zmianę stanu. W większość przypadków będą to żądania realizowane metodą POST. Ciasteczka oznaczone jako SameSite=Lax
będą przesłane bezpiecznie w żądaniach do stron najwyższego poziomu, np. przy kliknięciu w odnośnik przekierowujący do innej witryny. Jednak żądanie z formularza <form>
przesłane metodą POST do innej strony nie będzie zawierać ciasteczek.
Scenariusz (z użyciem formularza), służy do przekierowania użytkownika do zdalnej usługi w celu wykonania pewnych operacji przed powrotem, na przykład przekierowania do
zewnętrznego
dostawcy tożsamości. Zanim użytkownik opuści witrynę, ustawiane jest ciasteczko zawierające token jednorazowego użytku, który można porównać z powrotnym żądaniem, aby zminimalizować ataki typu cross site request forgery (CSRF) (niezorientowanym polecam artykuł Czym jest podatność CSRF?). Jeśli takie żądanie, np. z formularza logowania za pomocą usług federacyjnych AD FS lub Azure (w następnej części cyklu przeanalizujemy przebieg logowania i zobaczymy jak instalowane są ciasteczka przez i po zastosowaniu styczniowych pakietów poprawek dla Windows Server 2016 (KB4534271) i Windows Server 2019 (KB4534273)), zostanie wysłane za pośrednictwem POST, ciasteczka będą musiały być oflagowane jako SameSite=None; Secure
.
Zasoby zdalne
Żądanie dowolnego rodzaju zasobu zdalnego, przykładowo wczytywanego znacznikami
<img>
, <script>
lub innymi, może być zależne od ciasteczek. Szczególnie popularne są tzw.
„piksele”
śledzące i personalizujące treści.
Dotyczy to również żądań inicjowanych skryptami JavaScript za pomocą funkcji fetch
lub XMLHttpRequest
. Wywołanie fetch()
z parametrem credentials: 'include'
jest idealnym przykładem żądania, w którym oczekujemy załączenia ciasteczka. Dla XMLHttpRequest
powinniśmy poszukać wystąpień właściwości withCredentials
ustawionych na true
. W takich przypadkach, załączenie ciasteczka w żądaniu będzie oczekiwane. A co za tym idzie, takie ciasteczka muszą być skonfigurowane do użycia pomiędzy witrynami (w komunikacji typu cross-site).
Treści wyświetlane w WebView
WebView w Androidzie oraz WKWebView w iOS, umożliwiają zagnieżdżanie oraz natywną obsługę treści webowych w
aplikacjach. Prędzej lub później nowe zasady obsługi ciasteczek zostaną
zaimplementowane
w bibliotekach WebView. Obecnie Android zezwala na bezpośrednią instalację ciasteczek za pomocą CookieManager API, a iOS za pomocą
httpCookieStore. Ciasteczka są wystawiane w nagłówkach HTTP lub za pomocą skryptów JavaScript, wydaje mi się, że warto już teraz dodać atrybut SameSite=None; Secure
jeśli mają być stosowane pomiędzy stronami lub
zewnętrznymi komponentami
osadzonymi w aplikacjach.
Jak już dzisiaj zaimplementować SameSite
?
W przypadku plików cookie, w których są one potrzebne tylko w kontekście własnych, najlepiej oznaczyć je jako SameSite=Lax
lub SameSite=Strict
, w zależności od potrzeb. Słabszym rozwiązaniem jest zdanie się na implementacje domyślnych zachowań po stronie przeglądarek, bo stwarza to ryzyko niespójnego funkcjonowania aplikacji lub witryny webowej w różnych klientach oraz będzie generowało ostrzeżenia w konsoli.
Set-Cookie: ciastko_wlasne=wartosc; SameSite=Lax
Z kolei ciasteczka, które są potrzebne w kontekście zewnętrznych stron, najlepiej oznaczyć jako SameSite=None; Secure
. Koniecznie użyjcie obu atrybutów. Jeśli wybierzecie None
ale bez dodatkowego parametru Secure
ciasteczko zostanie odrzucone. Pozostaje jeszcze kwestia niekompatybilności pomiędzy różnymi przeglądarkami, ale o tym w następnej sekcji.
Set-Cookie: ciasteczko_zewnetrzne=wartosc; SameSite=None; Secure
Obsługa niezgodnych ze standardem klientów
Zmiany dotyczące trybu None
oraz domyślne zachowanie w przeglądarkach są stosunkowo nowe, co może generować niespójności w obsłudze. Na stronie chromium.org udostępniono (stale aktualizowaną) listę znanych problemów.
Chociaż nie jest to idealne rozwiązanie, istnieją sposoby obejścia problemu braku obsługi nowego trybu w fazie przejściowej, w której niekompatybilne aplikacje zginą lub zostaną dostosowane. Ogólną zasadą jest traktowanie klientów niekompatybilnych jako szczególnego przypadku, nie na odwrót. Nie twórzcie wyjątków dla przeglądarek implementujących nowe zasady.
Pierwszym sposobem na obejście problemu jest równoległa instalacja ciasteczek w „nowym” i „starym” stylu:
Set-cookie: wazne_ciastko=wartosc; SameSite=None; Secure
Set-cookie: wazne-ciastko-przestarzale=wartosc; Secure
Przeglądarki stosujące nowsze zachowanie pobiorą ciasteczko z wartością SameSite
, podczas gdy niekompatybilne przeglądarki prawdopodobnie je zignorują. Jednak, te przeglądarki, powinny ustawić ciasteczko wazne-ciasteczko-przestarzale
. Podczas przetwarzania dołączonych plików cookie witryna powinna najpierw sprawdzić obecność
„nowego” ciasteczka, a jeśli nie zostanie znalezione, przełączyć się na
„starsze”.
Poniższy przykład pokazuje, jak to zrobić w Node.js, korzystając z Express framework oraz metod cookie-parser.
const express = require('express');
const cp = require('cookie-parser');
const app = express();
app.use(cp());
app.get('/set', (req, res) => {
// Ustawienie ciasteczka nowa metoda
res.cookie('wazne-ciastko', 'wartosc', { sameSite: 'none', secure: true });
// Ustawienie ciasteczka z taka sama wartoscia stara metoda
res.cookie('wazne-ciastko
-przestarzale', 'wartosc
', { secure: true });
res.end();
});
app.get('/', (req, res) => {
let cookieVal = null;
if (req.cookies['wazne-ciastko
']) {
// Najpier sprawdz wartosc nowego ciasteczka
cookieVal = req.cookies['wazne-ciastko
'];
} else if (req.cookies['wazne-ciastko
-przestarzale
']) {
// Dopiero jesli nie istnieje sprawdz stare
cookieVal = req.cookies['wazne-ciastko
-przestarzale
'];
}
res.end();
});
app.listen(process.env.PORT);
Oczywistą wadą powyższego rozwiązania jest produkcja zbędnych plików cookie dla wszystkich przeglądarek, dodatkowo wymaga to wprowadzenia zmian zarówno w momencie ustawienia, jak i odczytu ciasteczka. Takie podejście pozwala obsłużyć wszystkie przeglądarki bez względu na ich zachowanie i zapewnić, że pliki cookie innych firm będą nadal działać tak jak poprzednio.
Alternatywnie można przed zastosowaniem instrukcji Set-Cookie
wykryć używaną przeglądarkę za pomocą user agent string (UA). Na liście niekompatybilnych klientów znajdziemy większość popularnych aplikacji, wówczas wystarczy skorzystać z odpowiedniej biblioteki, trzymając się powyższego przykładu w Node.js może to być ua-parser-js. Lepiej zdać się na gotowe biblioteki do wykrywania UA, żeby nie musieć samodzielnie konstruować wyrażeń regularnych.
Zaletą tego podejścia jest to, że wymaga tylko jednej zmiany w momencie ustawienia pliku cookie. Niestety wykrywanie funkcjonalności po identyfikatorze UA pachnie Internetem 1.0 i w wielu wypadkach może być zawodne. Pamiętajcie też, że obecnie wszyscy udają wszystkich, zresztą sprawdźcie sami na What’s my UA.
Bez względu na wybraną opcję warto monitorować, jaki jest poziomo korzystania ze starszej metody. A nawet ustawić sobie przypomnienie, aby wycofać obejście, gdy poziomy te spadną poniżej akceptowalnego progu.
Obsługa SameSite=None
w językach, bibliotekach i frameworkach
Większość języków i bibliotek obsługuje atrybut SameSite
w ciasteczkach, jednak już SameSite=None
jest stosunkowo nowe,co oznacza, że na razie może być konieczne obejście niektórych standardowych zachowań. Są one udokumentowane w repozytorium przykładów
SameSite
na GitHub-ie.
Gdzie szukać pomocy?
Nie przesadzę stawiając tezę, że ciasteczka są wszędzie. I rzadko zdarza się, aby jakakolwiek strona w pełni kontrolowała tryb ich instalacji i użycia, w szczególności na stronach zewnętrznych i w scenariuszach mieszanych. Jeśli już zderzycie się z problemem, proponuję zacząć poszukiwania pomocy w tych miejscach:
- Zgłoszenie problemu w repozytorium przykładów
SameSite
na GitHub. - Pytanie pod tagiem “samesite” na StackOverflow.
- Jeśli sądzicie, że trafiliście na problem wynikający z implementacji w przeglądarce możecie zgłosić problem na trackerze producenta lub społeczności, np. [SameSite cookies] issue.
Tekst jest częściowo oparty na artykule SameSite cookie recipes autorstwa
Rowana Merewooda.
Twoje okno na chmurę