Cookies

Ciasteczka to małe kawałki tekstu przechowywane przez przeglądarkę, a które najczęściej służą zarówno do zapisywania krótkich informacji takich jak np. id użytkownika, numer aktualnej sesji, preferowany tym reklam itp., ale równocześnie też do przesyłania takich danych między serwerem a przeglądarką, dzięki czemu obie strony mają do nich dostęp.

W rozdziale o AJAX omawialiśmy sobie protokół HTTP. Komunikacja między przeglądarką a serwerem odbywa się podobne do gry w tenisa. Przeglądarka pyta się o jakiś zasób, serwer odpowiada. W takiej komunikacji obie strony wysyłają sobie odpowiednie dane, do których dołączane są różne nagłówki. Przeglądarka wysyłając zapytanie do serwera wysyła "request headers". Gdy serwer odpowiada wraz z odpowiedzią wysyła "response headers".

Wśród tych nagłówków są dwa związane z przekazywaniem informacji o ciasteczkach.

Jeżeli kod tworzący ciasteczko został odpalony po stronie serwera, wraz z odpowiedzią wyśle on dla każdego ciastka nagłówek Set-Cookie. Przeglądarka odczyta taką odpowiedź, i jeżeli natrafi taki nagłówek automatycznie utworzy sobie odpowiednie ciastko.

I to właśnie ten kierunek jest tym najczęściej spotykanym. Serwer wysyła nam powyższy nagłówek z np. danymi autoryzacyjnymi, a nasza przeglądarka tworzy odpowiednie ciasteczko.

Wykonując kolejne zapytania do serwera przeglądarka będzie dołączać do nich informacje o ciasteczkach dla danej strony za pomocą nagłówka Cookie.

Po stronie serwera utworzyłem dla ciebie 2 ciasteczka: userid i age. Przejdź do zakładki Network w debugerze, odśwież stronę i zbadaj pierwszy request na liście: cookies response

Ciasteczka obecne na danej stronie możesz sprawdzić na kilka sposobów. Wystarczy, że wpiszesz w konsoli debugera document.cookie - wtedy zobaczysz listę ciasteczek. O wiele lepiej jednak skorzystać z zakładki Application, w której pod zakładką Cookies znajdziesz wszystkie ciasteczka wraz z możliwością ich edycji i usuwania:

Zakładka Application w debugerze

Ciasteczka możesz tworzyć zarówno na czas sesji (wtedy zostaną usunięte po zamknięciu przeglądarki), lub na czas który sam określisz. Dzięki temu możesz je wykorzystać nie tylko do przekazywania na serwer i z powrotem prostych danych, ale też do zapisania w przeglądarce kilku dodatkowych informacji dla użytkownika - np. jaką wybrał skórkę na stronie, czy wyraził chęć by go nie wylogować itp. W czasach kiedy nie mieliśmy dostępu do Storage, ciasteczka były główną formą zapisywania takich mikro informacji. W dzisiejszych czasach wygodniejsze jest używanie Storage, natomiast samych ciasteczek używamy wszędzie tam gdzie przydało by się mieć dostęp do tych danych po stronie serwera.

Większość skryptów z tego działu będziesz musiał odpalić z serwera lokalnego.
Obostrzenia względem ciasteczek są bardzo podobne do same-origin policy, czyli zabezpieczeń, które nie pozwalają łączyć się asynchronicznie (ajax) gdzie tylko chcemy. Najczęściej ograniczeni jesteśmy do własnej domeny, chyba, że domena z którą się komunikujemy, ustawi odpowiednie nagłówki.

document.cookie

Powyżej powiedziałem ci, że do sprawdzenia listy ciastek na danej stronie możesz skorzystać z właściwości document.cookie. Właściwość ta służy zarówno do wyświetlania listy aktualnych ciastek dla danej strony, ustawiania nowych, ale też do ich usuwania. Nie jest to jednak właściwość, która odzwierciedla stan ciastek w 100%. Można ją traktować raczej jako mini interfejs służący do zarządzania nimi.

Gdy wpiszesz ją w konsoli, zobaczysz listę ciastek w postaci:


userid=810a37e4bb9152465d4c42e43c5af941; age=25; ...

Gdy przejdziesz jednak do zakładki Application w debugerze, zobaczysz, że dane ciastka mają ciut więcej parametrów (patrz poniżej).

Gdy będziesz chciał utworzyć nowe ciastko, ustawisz tej właściwości nową wartość. Nie sprawi to jednak, że nadpiszesz wszystkie poprzednie ciastka, bo właśnie ustawiłeś całkowicie nową wartość. Nie - tylko dodasz nowe. Taka to specyficzna właściwość...

Tworzymy pierwsze ciasteczko

Stworzenie ciasteczka może być inicjowane po stronie serwera, ale tym bardziej możemy je stworzyć np. za pomocą Javascript.
Aby to zrobić, musimy w odpowiedni sposób ustawiając wartość dla właściwości document.cookie:


document.cookie = "nazwa=wartosc"

Ustawienie dla document.cookie nowej właściwości nie skasuje wszystkich poprzednio ustawionych ciastek, a utworzy nowe (jeżeli takiego nie ma).

Teoretycznie nazwa i wartość ciastka może zawierać dowolne znaki. Dla zachowania poprawnego formatowania, warto zastosować tutaj funkcję encodeURIComponent():


document.cookie = encodeURIComponent("nazwa użytkownika") + "=" + encodeURIComponent("Karol Nowak");
console.log(document.cookie); //"nazwa%20u%C5%BCytkownika=Karol%20Nowak"

Postać ciasteczka

Każde ciasteczko może składać się z kilku części:


document.cookie = "user=John; path=/; expires=Tue, 19 Jan 2038 03:14:07 GMT; secure"
path

Oznacza zakres ciasteczka, czyli z jakiego adresu będzie do niego dostęp.

Jeżeli dla przykładu ustawimy path=/user, dostęp do tego ciasteczka będzie miała podstrona /user, /user/inventory ale już nie strona /pet i potomne.

domain

Z jakiej domeny będzie dostęp do danego ciasteczka. Domyślnie ciasteczka są dostępne tylko z domeny, z której zostały ustawione.

Jeżeli ustawisz ciasteczko na adresie test.com, domyślnie nie będziesz miał do niego dostępu pod adresem auction.test.com. Jeżeli chciałbyś by taki dostęp był możliwy, powinieneś ustawić domenę na domain=test.com:


        document.cookie = "user=John";

        //na auction.test.com
        console.log(document.cookie); //""
        

        document.cookie = "user=John; domain=test.com";

        //na auction.test.com
        console.log(document.cookie); //"user=John"
        
expires i max-age

Po ustawieniu ciasteczka będzie one aktywne do zamknięcia przeglądarki. Parametr expires oznacza datę do której dane ciastko będzie aktywne:


        //plus jeden dzień od teraz
        let date = new Date(Date.now() + 86400e3);
        date = date.toUTCString();
        document.cookie = "user=John; expires=" + date;
        

Ustawienie daty na mniejszą od obecnej spowoduje, że dane ciasteczko zostanie usunięte.

Alternatywnym zapisem dla expires jest max-age, który oznacza liczbe sekund od teraz, po których dane ciastko zostanie usunięte:


        document.cookie = "user=John; max-age=3600";
        
secure

Domyślnie gdy założymy ciasteczko, dostęp do niego będzie możliwy zarówno z adresu http:// jak i https://. Jeżeli użyjemy parametru secure, dostęp będzie możliwy tylko z https://:


        document.cookie = "user=John; max-age=3600; secure";
        
httpOnly

Jeżeli serwer wyśle do nas nagłówek Set-Cookie, który będzie zawierał parametr httpOnly, nie będziemy mieli dostępu do tego ciasta z poziomu Javascript.

samesite

Parametr samesite służy głównie do zabezpieczenia przed atakiem CSRF czy Pixel Perfect.

Funkcje do tworzenia ciastek

Dla ułatwienia tworzenia ciastek wraz z parametrami, możemy utworzyć funkcję:


function setCookie(name, value, options) {
    const opts = {
        path: "/",
        ...options
    }

    if (navigator.cookieEnabled) { //czy ciasteczka są włączone
        const cookieName = encodeURIComponent(name);
        const cookieVal = encodeURIComponent(value);
        let cookieText = cookieName + "=" + cookieVal;

        if (opts.days && opts.days === "number") {
            const data = new Date();
            data.setTime(data.getTime() + (opts.days * 24*60*60*1000));
            cookieText += "; expires=" + data.toUTCString();
        }

        if (opts.path) {
            cookieText += "; path=" + opts.path;
        }
        if (opts.domain) {
            cookieText += "; domain=" + opts.domain;
        }
        if (opts.secure) {
            cookieText += "; secure";
        }

        document.cookie = cookieText;
    }
}

setCookie("mojeCiasteczko", "przykladowa wartość");
setCookie("mojeCiasteczko", "przykladowa wartość", { days: 10, path: "/" });

Odczyt ciasteczka

Aby odczytać dane ciasteczko, także skorzystamy z właściwości document.cookie.


function getCookie(name) {
    if (document.cookie !== "") {
        const cookies = document.cookie.split(/; */);

        for (let cookie of cookies) {
            const [ cookieName, cookieVal ] = cookie.split("=");
            if (cookieName === decodeURIComponent(name)) {
                return decodeURIComponent(cookieVal);
            }
        }
    }

    return undefined;
}

console.log(getCookie("mojeCiasteczko"));

Usuwanie ciasteczka

Aby usunąć dane ciasteczko musimy ustawić jego parametr expires na wcześniejszy od aktualnej daty:


function deleteCookie(name) {
    const cookieName = encodeURIComponent(name);
    document.cookie = cookieName + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT";
}

deleteCookie("darkTheme");

Może się jednak zdarzyć, że będziesz chciał usuwać ciasteczko, któremu wcześniej ustawiłeś właściwość path. W takim przypadku musisz także podać tą wartość:


function deleteCookie(name, path) {
    const opts = {
        path: "/",
        ...options
    }

    const cookieName = encodeURIComponent(name);
    let cookieText = cookieName + "=";
    if (opts.path) {
        cookieText += "; path=" + opts.path;
    }
    cookieText += "; expires=Thu, 01 Jan 1970 00:00:00 GMT";
    document.cookie = cookieText;
}

deleteCookie("darkTheme");
deleteCookie("darkTheme");

Wszelkie prawa zastrzeżone. Jeżeli chcesz używać jakiejś części tego kursu, skontaktuj się z autorem.
Aha - i ta strona korzysta z ciasteczek.