Jak przechowywać dane w przeglądarce cz.2
W ostatnim wpisie z tej serii przejrzeliśmy, jakie możliwości oferują nam web storage api. Jeśli jeszcze nie znalazła się wolna chwila, aby go przeczytać, link znajdziesz tutaj
Dzisiaj na tapet weźmiemy kolejną opcję przechowywania danych w przeglądarce
Cookies
Ciasteczka to nic innego jak dane trzymane w przeglądarce w formie tekstowej. Mogą zostać utworzone zarówno za pośrednictwem JS’a, jak i za pomocą requestu HTTP z serwera.
Ten typ storage zdecydowanie różni się od poprzedników. Na pierwszy rzut oka ograniczenia są dużo większe, bo mamy do dyspozycji:
- tylko 4kb pamięci
- nie więcej niż 20 ciasteczek dla każdej domeny
Jednakże przy odpowiednim wykorzystaniu ciasteczka mogą być również najbezpieczniejszym frontendowym rozwiązaniem
Tworzenie cookies z poziomu JS’a
Dostęp do ciasteczek z poziomu JavaScript’a uzyskujemy za pośrednictwem klucza document.cookie. Zwróci on stringa oddzielonego średnikami gdzie każdy atrybut ciasteczka będzie stringiem klucz=wartość
Przykładowo:
document.cookie = „instytut=fullstack; domain=instytutfullstack.pl; Secure”
Utworzy nam ciasteczko o kluczu instytut z wartością fullstack oraz dwoma dodatkowymi atrybutami
Co ciekawe taki zapis nie nadpisze nam ciasteczek, jeśli byłyby już jakieś, a jedynie doda nowe lub zmodyfikuje, jeśli występują z tą samą nazwą.
Sterowanie ich zachowaniem oraz cechami za pomocą atrybutów
- Domain
Tym atrybutem określamy, dla jakiej domeny ma być dostępne ciastko. Warto zaznaczyć, że gdy nie sprecyzujemy powyższego atrybutu i wykonamy
document.cookie = "author=Krzysztof"
Na domenie instytutfullstack.pl to utworzone w ten sposób ciasteczko nie będzie dostępne dla subdomen, czyli np. test.instytutfullstack.pl W celu umożliwienia takiego dostępu należy wyraźnie zdefiniować atrybut domain:
document.cookie = "author=Krzysztof; domain=instytutfullstack.pl"
-
Path Za pomocą path ustawiamy ścieżkę, dla której ciastko jest widoczne. W praktyce zwykle stosuje się path=/ żeby dane były dostępne na całej witrynie
-
Expires Pozwala ustawić datę, do której dane będą dostępne Bardzo ważne jest to, żeby ustawić ją w formacie UTC dla strefy czasowej GMT Przykład, jak dodać ciastko o "dacie przydatności do spożycia" 5 dni:
const date= new Date(); date.setDate(date.getDate() + 5); date.toUTCString() document.cookie = "user=John; expires=" + date;
- Max-age Określa czas życia ciastka od momentu utworzenia. Jego wartość jest liczona w sekundach. Przykładowo zapis:
document.cookie = "author=Krzysztof; max-age=600";
Sprawi, że author=Krzysztof będzie dostępne przez 10 min od utworzenia
-
Secure Atrybut typu boolean oznaczający możliwość dostępu do danych tylko za pośrednictwem szyfrowanego protokołu https
-
HttpOnly Parametr używany z poziomu serwera. Jego dodanie oznacza, że kod JavaScript klienta, do którego serwer wysyła ciastko, nie będzie w stanie go odczytać (może być ono jedynie odesłane z powrotem na serwer)
-
SameSite Stosowany głównie ze względów bezpieczeństwa i posiada dwie możliwe wartości: strict oraz lax. To złożony temat wymagający omówienia w osobnym artykule, dlatego tutaj tylko krótko opiszemy, o co chodzi.
W telegraficznym skrócie opcja strict nie pozwala dodać ciastka do requestu który pochodzi z innej domeny (obrona przed atakiem XSRF)
Z kolei lax jest nieco bardziej luźne, bo mimo tego, że również stosuje mechanizm, jak strict to posiada pewne wyjątki w swej surowości. Mianowicie lax przymknie oko, gdy request należy do grupy "bezpiecznych metod HTTP (np. GET, HEAD, OPTIONS)" oraz został wysłany w wyniku operacji "top-level navigation", czyli np naciśnięcia przycisku wstecz w przeglądarce. Należy jednak pamiętać, że ten wyjątek nie dotyczy iframów
Z istotnych informacji, warto dodać, że co stanie się, jeżeli równocześnie dodamy atrybuty Expires oraz max-age. W przypadku przeglądarek wspierających Max-age tylko on będzie wzięty pod uwagę, a expires zostanie zignorowany.
Natomiast jeśli nie określimy czasu "życia" ciastka za pomocą żadnego z powyższych atrybutów, to zostanie ono usunięte po zamknięciu przeglądarki.
Jak łatwo zauważyć domyślne API do obsługi ciasteczek, nie jest zbyt przyjazne, więc polecamy skorzystać z jakiejś gotowej biblioteki jak np. js-cookie
Przykład użycia js-cookie:
Cookies.set('name', 'value', { expires: 7, path: '' })
Wykorzystując obiekty w JavaScript o wiele wygodniej nam sterować ciasteczkami. Zamiast "sklejać stringi" możemy dodawać klucze do obiektu
Tworzenie cookies na serwerze
Jesteśmy w stanie również stworzyć ciasteczko po stronie serwera i wysłać je w odpowiedzi na request http. Należy w tym celu użyć nagłówka "Set-Cookie", który sprawi, że ciasteczko zostanie zapisane po stronie klienta.
Istnieje także możliwość stworzenia ciasteczka, do którego nie będzie dostępu za pośrednictwem document.cookie, czyli z poziomu JS’a. Należy w tym celu dodać atrybut httpOnly do ciastka ustawianego za pośrednictwem wyżej wspomnianego Set-Cookie. Takie ciastko może być jedynie odesłane z powrotem na serwer.
Po co nam coś takiego?
Jednym z przykładów użycia, może być obsługa sesji użytkownika. Wysyłamy wtedy ciastko z serwera o kluczu np. sessionId i wartości sesji, a także atrybutem expires
W tym przypadku klient nie ma potrzeby dostępu do tych danych. Możemy więc ograniczyć ryzyko wycieku i zabronić wglądu do ciastka z poziomu kodu.
Usuwanie ciasteczka
Niestety nie ma dostępnej metody clear ani delete. Żeby usunąć ciasteczko, musimy posłużyć się trickiem i np. ustawić atrybut expires na datę wstecz, dzięki czemu ciasteczko "samo się usunie"
Zarówno atrybuty max-age, jak i expires, będą działać. Oto przykłady dla obu przypadków:
document.cookie = "author=Krzysztof; expires=Thu, 01 Jan 1970 00:00:00 GMT"
lub
document.cookie = "author=Krzysztof; max-age=0";
Third party cookies
Obcując z cookiesami z pewnością prędzej czy później zetkniesz się z nazwą Third-party cookies. Ciastko możemy nazwać third-party, gdy jest umieszczone przez inną witrynę niż strona odwiedzana bezpośrednio przez nas
Aby lepiej to zobrazować, posłużmy się przykładem z obrazkami. Wyobraź sobie, że na naszej stronie instytutfullstack.pl umieścilibyśmy jakiś baner służący do płatnej reklamy. Adres skąd może zostać pobrany to np. https://example.com/advertisement.jpg
Wysyłając request GET w celu pobrania reklamy strona, która nam go udostępnia, może ustawić nagłówek Set-cookie i tym samym w odpowiedzi zwrócić ciasteczko z jakimś trackId
Łatwo zauważyć, że na ostatnim obrazku mamy do czynienia z sytuacją gdzie, nawet jeśli wchodzimy na inną witrynę korzystających z tych samych dostawców reklam ciastko, które zostało nam wysłane dalej może nas śledzić
Zobacz, wystarczy chwila nieuwagi, aby przez przypadek wejść na stronę, która utworzy third party cookie, a ono będzie podążać Twoim śladem w Internecie
Oczywiście third party cookies nie zawsze mają ten sam cel. Natomiast zdecydowana większość przypadków opiera się o potrzeby analityki lub reklam
Niektóre przeglądarki jak np. Safari domyślnie blokują takie zachowanie jednakże nie jest to reguła
Podsumowanie
Mamy nadzieję, że po tym wpisie o wiele bardziej świadomie będziesz stosować ciastka w swoich projektach. Jest to stary, lecz wciąż bardzo ważny element web developmentu. Poniżej umieszczamy kilka punktów podsumowujących, do których dla ułatwienia możesz wrócić i przypomnieć sobie, kiedy zastosować ciastka, a kiedy lepiej z nich zrezygnować
Warto zastosować ciastka:
- Gdy chcemy używać zapisanych danych także po stronie serwera
- Jeżeli znamy dokładną datę, w której chcemy się ciastka pozbyć
- Jeśli chcemy zabezpieczyć dane przed dostępem z poziomu JS’a, ale nadal chcemy przechowywać je w przeglądarce
Lepiej zrezygnować z cookies:
- W przypadku potrzeby przechowywania większej ilości informacji
- Jeśli nie chcemy określać dokładnego momentu usunięcia danych
- Do przechowywania wrażliwych informacji takich jak dane osobowe itp.
Na sam koniec przykład z życia wzięty: W ciastku httpOnly + secure + SameSite trzymamy tokeny JWT lub informacje o sesji obsługiwanej przez backend