Neuroróżnorodność w miejscu pracy
28.03.20236 min
Stefan Haas

Stefan HaasAngular Trainer & ConsultantAngular Architects

Nie dla tokenów uwierzytelniających na frontendzie

Poznaj nowoczesne podejście do budowy bezpiecznego procesu uwierzytelniania dla aplikacji jednostronicowych.

Nie dla tokenów uwierzytelniających na frontendzie

W tym agnostyczno-frameworkowym artykule przedstawię Ci nowoczesne podejście do budowania bezpiecznych procesów uwierzytelniania dla aplikacji jednostronicowych. Jeśli znasz już tokeny JWT i OAuth 2.0, to istnieje szansa, że wyniesiesz coś z tej lektury, ponieważ artykuł wyjaśnia, dlaczego nie należy przechowywać tokenów w przeglądarce i pokaże nowoczesne podejście do łączenia ciasteczek i tokenów JWT, aby tworzyć bezpieczniejsze aplikacje.

OAuth 2.0

 Dostępnych jest wiele dobrych artykułów doskonale wyjaśniających szczegóły OAuth 2.0, więc zamiast tego skupię się na przedstawianiu bardzo uproszczonego przeglądu OAuth 2.0. Tak więc, w zasadzie OAuth 2.0 jest de facto standardem przemysłowym do tworzenia bezpiecznego przepływu uwierzytelniania dla dowolnego rodzaju aplikacji. Klient wysyła żądanie logowania do dostawcy uwierzytelniania, który następnie wysyła 302 Redirect, który uruchamia przeglądarkę, aby przekierować do strony logowania dostarczonej przez dostawcę uwierzytelniania.

Po zalogowaniu dostawca uwierzytelniania przekierowuje z powrotem do oryginalnej trasy, na której klient uruchomił proces logowania, a dodatkowo wysyła token dostępu i token odświeżania, które są używane do uwierzytelniania w przypadku korzystania z uwierzytelniania na okaziciela (de facto standardowy).

Tokeny dostępu są dołączane do każdego nagłówka żądania HTTP jako parametr autoryzacji, dzięki czemu API może sprawdzić z dostawcą uwierzytelniania, czy token jest ważny. Zazwyczaj token dostępu ma niski czas wygaśnięcia, który czasem wynosi nawet kilka minut, co pozwala na większe bezpieczeństwo, ponieważ złośliwa strona nie mogłaby używać tokena dostępu wystarczająco długo, aby wykonać wiele złośliwych żądań.

Tokeny odświeżania są następnie używane przez klienta, aby uzyskać nowy token dostępu od dostawcy uwierzytelniania, gdy bieżący token dostępu wygasa.

Oba te tokeny są uważane za JWT (JSON Web Token), który zasadniczo jest obiektem JSON zawierającym standardowe dane, ale także dane aplikacji.

OAuth 2.0 jest wygodny i pozwala na łatwe integracje z dostawcami tożsamości stron trzecich, takimi jak Google Identity, Facebook, Microsoft, Azure Active Directory itp. Tak więc uruchamiamy proces logowania i po prostu przechowujemy token dostępu i token odświeżania w lokalnej pamięci, a następnie dołączamy token do nagłówków HTTP. Takie to proste. Ale...

Atak XXS (Cross-Site Scripting)

Byłoby super, gdyby nie było tej uciążliwej rzeczy zwanej Cross-Site Scripting. W skrócie: XSS. Jeśli zależy nam na bezpieczeństwie i ochronie przed XSS, nie możemy tak po prostu przechowywać tokenów w lokalnej pamięci.

XSS jest techniką hakerską używaną do wstrzykiwania niestandardowego JavaScriptu do DOM w czasie działania. Przykładowo, haker mógłby wstrzyknąć kod JavaScriptu w pole wejściowe, wyprowadzając dane wejściowe za pomocą znaku “-”, a następnie wykonując niestandardowy kod JavaScriptu, gdy tymczasem każdy interfejs API JavaScriptu jest dostępny dla złośliwego napastnika. Dlatego atakujący może łatwo odczytać dane wejściowe z lokalnej pamięci, a tokeny wpadają w jego ręce. Od tego momentu możesz mieć tylko nadzieję na jak najlepszy przebieg sytuacji, i jednocześnie żałować, że nie włożyłeś więcej wysiłku w zabezpieczenie aplikacji. Jakie jest więc rozwiązanie?

Wzorzec obsługi tokena

Cofnijmy się nieco w czasie i porozmawiajmy o starszym podejściu do uwierzytelniania między aplikacjami frontendowymi i backendowymi. Za starych dobrych czasów nie mieliśmy tych wymyślnych tokenów, ale używaliśmy prostych plików cookie do weryfikacji, czy użytkownik jest zalogowany. Wtedy frontend wysyłał nazwę użytkownika i hasło do backendu, który następnie tworzył plik cookie przypominający stan autoryzacji i ustawiał go w nagłówkach HTTP, po czym frontend wysyłał plik cookie w nagłówkach HTTP w każdym żądaniu.

Wróćmy do naszego przykładu, gdzie problem polega na tym, że przechowujemy tokeny na kliencie. Niestety, nie ma sposobu, aby bezpiecznie przechowywać tokeny na kliencie, ponieważ nie chcemy, aby JavaScript miał do nich dostęp. Jedynym sposobem na obejście tego problemu jest stworzenie dedykowanego w tym celu backendu, który obsługuje logowanie.

Frontend komunikuje się tylko z oprogramowaniem pośrednim, które obsługuje uwierzytelnianie / autoryzację i działa jako proxy między API a frontendem. Oprogramowanie uruchamia przekierowanie HTTP 302, które automatycznie wymusi na przeglądarce przekierowanie na stronę logowania dostarczoną przez dostawcę tożsamości. Po zalogowaniu dostawca tożsamości przekierowuje do redirect_url, który musi być określony w oryginalnym żądaniu przekierowania. Redirect_url wskazuje na dedykowaną trasę na frontendzie, podczas gdy dostawca tożsamości doda kod uwierzytelniający do parametrów zapytania. Następnie frontend wyśle kod uwierzytelniający do backendu, który używa kodu uwierzytelniającego do generowania tokenów. Te tokeny będą przechowywane w ciasteczkach HTTP-only, który jest ustawiony w nagłówkach HTTP w odpowiedzi na frontend. Następnie frontend przechowuje plik cookie w przeglądarce i poprzez dodanie parametru credentials: include do początkowego żądania.

Ale co dokładnie oznacza HttpOnly i dlaczego ma to być bardziej bezpieczne niż przechowywanie tokena na kliencie?

Ciasteczka z HTTPOnly

Dlaczego przechowywanie tokena w pliku cookie powinno być bardziej bezpieczne niż w lokalnej pamięci? Otóż domyślnie nie jest, ponieważ API JavaScriptu może również łatwo odczytać wszystkie pliki cookie poprzez dostęp do document.cookie. Więc kolejnym zmartwieniem jest XSS.

Ale... na szczęście możemy skorzystać z tzw. ciasteczek HTTP-only, do których nie ma dostępu API JavaScriptu. Mimo że są one przechowywane w przeglądarce dokładnie tak samo jak zwykłe pliki cookie, mogą być dołączane tylko do żądań HTTP. Na przykład możesz ustawić opcję credentials:'include' pobierania danych z API, aby uwzględnić wszystkie pliki cookie, w tym ciasteczek HTTP-only, w nagłówkach żądania. W ten sposób atakujący XSS nie ma możliwości zdobycia ciasteczka HTTP-only, a więc nie uda mu się przejąć tokenów.

Własne pliki cookie i pliki cookie stron trzecich

Pozostaje jeszcze jedna rzecz, którą musimy omówić - różnica między własnymi plikami cookie i plikami cookie stron trzecich. Plik cookie utworzony w innej domenie jest zawsze uważany za plik cookie strony trzeciej, natomiast plik cookie utworzony w domenie, w której hostowany jest frontend, jest uważany za własny plik cookie.

Nierzadko zdarza się, że backend jest hostowany na innej domenie, zwłaszcza gdy frontend jest aplikacją jednostronicą (Single Page Application). W takim przypadku możliwy jest nadal atak XSS, ponieważ atakujący mógłby wstrzyknąć kod, który wykonuje żądanie HTTP do własnego interfejsu API i zawiera pliki cookie, takie, że jego API odczyta plik cookie HTTP-only.

Na szczęście istnieje również sposób, aby temu zapobiec, poprzez stworzenie własnego pliku cookie i ustawienie parametru nagłówka SameSite='strict', tak aby plik cookie był wysyłany tylko do żądań skierowanych do tej samej domeny. Trzeba by po prostu zastosować oprogramowanie pośrednie na tej samej domenie.

Bezserwerowa obsługa tokena

Chociaż nie jest to konieczne, jest to idealny scenariusz do wykorzystania przetwarzania bezserwerowego, zamiast tworzenia dedykowanego API dla pośredniego oprogramowania.

Przetwarzanie bezserwerowe to usługa w chmurze, która pozwala na tworzenie i wdrażanie prostych funkcji bezstanowych bez konieczności myślenia o infrastrukturze i serwerach. Choć nazwa może być myląca, funkcje nadal będą uruchamiane na serwerze hostowanym przez dostawcę chmury, ale nie musimy myśleć o serwerach. Mogą być one skalowane w zależności od potrzeb, a także zmniejszane, dzięki czemu płacisz tylko za rzeczywiste wykorzystanie.

Możemy stworzyć dwie proste funkcje, przez co całe API sieciowe przeznaczone tylko dla oprogramowania pośredniego staje się zbędne. Pierwsza funkcja byłaby funkcją logowania, a druga funkcją proxy, która zawsze przekierowuje żądanie do API i dołącza token dostępu w nagłówkach.

Podsumowanie

Szczerze mówiąc, żaden system nigdy nie jest całkowicie bezpieczny. Ale ta hipoteza nie powinna nas powstrzymywać przed tworzeniem aplikacji, które są bardziej bezpieczne. Takie podejście może powstrzymać ataki XSS mogące pobrać tokeny JWT, ale wprowadza ryzyko CSRF, które zasadniczo jest atakiem, w którym atakujący wysyła żądanie od klienta bezpośrednio, używając jego ciasteczka. Żadne rozwiązanie nie jest idealne, ma swoje wady i zalety, ale naszym zadaniem jako inżynierów oprogramowania jest stworzenie odpowiedniego rozwiązania dla naszego przypadku biznesowego.



Oryginał tekstu w języku angielskim przeczytasz tutaj.

<p>Loading...</p>