Przejdź do treści głównej
Wersja: 0.79

Bezpieczeństwo

Nieoficjalne Tłumaczenie Beta

Ta strona została przetłumaczona przez PageTurner AI (beta). Nie jest oficjalnie zatwierdzona przez projekt. Znalazłeś błąd? Zgłoś problem →

Bezpieczeństwo często jest pomijane podczas tworzenia aplikacji. Choć prawdą jest, że nie da się stworzyć całkowicie nieprzenikalnego oprogramowania — w końcu nawet sejfy bankowe padają ofiarą włamań — to jednak ryzyko ataku lub ujawnienia podatności jest odwrotnie proporcjonalne do wysiłku włożonego w ochronę aplikacji. Nawet zwykła kłódka, choć podatna na wytrychy, stanowi znacznie lepszą ochronę niż zwykły haczek!

W tym przewodniku poznasz najlepsze praktyki przechowywania wrażliwych danych, uwierzytelniania, zabezpieczeń sieciowych oraz narzędzi pomocnych w ochronie aplikacji. To nie jest lista kontrolna przed wdrożeniem — to katalog opcji, z których każda dodatkowo wzmocni bezpieczeństwo twojej aplikacji i użytkowników.

Przechowywanie wrażliwych danych

Nigdy nie umieszczaj wrażliwych kluczy API w kodzie aplikacji. Każdy element kodu może zostać odczytany w postaci jawnej przez osobę analizującą bundle aplikacji. Narzędzia takie jak react-native-dotenv czy react-native-config świetnie nadają się do przechowywania zmiennych środowiskowych (np. endpointów API), ale nie należy ich mylić ze zmiennymi po stronie serwera, które często zawierają sekrety i klucze API.

Jeśli musisz użyć klucza API lub sekretu do zasobów, najbezpieczniejszym rozwiązaniem jest stworzenie warstwy pośredniczącej między aplikacją a docelowym zasobem. Może to być funkcja serverless (np. AWS Lambda lub Google Cloud Functions), która przekaże żądanie z wymaganym kluczem API. Sekrety w kodzie serwerowym nie są dostępne dla konsumentów API w taki sposób jak sekrety w kodzie aplikacji.

W przypadku trwałych danych użytkownika dobierz odpowiedni typ przechowywania w zależności od ich wrażliwości. Podczas korzystania z aplikacji często zachodzi potrzeba zapisywania danych na urządzeniu — czy to dla obsługi trybu offline, redukcji ruchu sieciowego, czy przechowywania tokenów dostępu między sesjami, aby użytkownik nie musiał się ponownie uwierzytelniać.

Dane trwałe a nietrwałe — dane trwałe są zapisywane na dysku urządzenia, co umożliwia ich odczyt między uruchomieniami aplikacji bez potrzeby ponownego pobierania z sieci lub prośby o ponowne wprowadzenie przez użytkownika. Jednak zwiększa to ryzyko dostępu atakujących do tych danych. Dane nietrwałe nigdy nie trafiają na dysk — więc nie ma danych do przechwycenia!

Async Storage

Async Storage to utrzymywany przez społeczność moduł React Native zapewniający asynchroniczny, nieszyfrowany magazyn klucz-wartość. Async Storage nie jest współdzielony między aplikacjami — każda aplikacja działa w własnym środowisku piaskownicy bez dostępu do danych innych aplikacji.

Do use async storage when...Don't use async storage for...
Persisting non-sensitive data across app runsToken storage
Persisting Redux stateSecrets
Persisting GraphQL state
Storing global app-wide variables

Uwagi dla programistów

Async Storage is the React Native equivalent of Local Storage from the web

Bezpieczne przechowywanie

React Native nie posiada wbudowanych mechanizmów przechowywania wrażliwych danych. Istnieją jednak gotowe rozwiązania dla platform Android i iOS.

iOS - Keychain Services

Keychain Services umożliwia bezpieczne przechowywanie niewielkich porcji wrażliwych danych użytkownika. To idealne miejsce dla certyfikatów, tokenów, haseł i innych danych, które nie powinny trafić do Async Storage.

Android - Bezpieczne preferencje współdzielone

Shared Preferences to odpowiednik trwałego magazynu klucz-wartość w Androidzie. Dane w Shared Preferences domyślnie nie są szyfrowane, ale Encrypted Shared Preferences opakowuje tę klasę i automatycznie szyfruje zarówno klucze, jak i wartości.

Android - Keystore

System Android Keystore umożliwia przechowywanie kluczy kryptograficznych w kontenerze utrudniającym ich wydobycie z urządzenia.

Aby korzystać z usług iOS Keychain lub Android Secure Shared Preferences, możesz samodzielnie napisać mostek komunikacyjny lub użyć biblioteki, która je opakowuje i udostępnia ujednolicone API — jednak robisz to na własne ryzyko. Oto kilka bibliotek wartych rozważenia:

Uważaj na przypadkowe przechowywanie lub ujawnianie wrażliwych danych. Może się to zdarzyć niechcący, np. poprzez zapisanie wrażliwych danych formularza w stanie Redux i utrwalenie całego drzewa stanu w Async Storage. Albo wysyłanie tokenów użytkowników i danych osobowych do usług monitorowania aplikacji jak Sentry czy Crashlytics.

Uwierzytelnianie i deep linking

Aplikacje mobilne mają unikalną lukę w zabezpieczeniach, nieistniejącą w środowisku webowym: deep linking. Deep linking to sposób przesyłania danych bezpośrednio do natywnej aplikacji z zewnętrznego źródła. Głębokie linki mają postać app://, gdzie app to schemat twojej aplikacji, a wszystko po // może być używane wewnętrznie do obsługi żądania.

Na przykład, gdybyś budował aplikację e-commerce, mógłbyś użyć app://products/1 jako deep linku otwierającego w aplikacji stronę szczegółów produktu o ID 1. Można to porównać do adresów URL w sieci, ale z jednym kluczowym rozróżnieniem:

Deep linki nie są bezpieczne i nigdy nie należy przesyłać nimi wrażliwych informacji.

Powodem jest brak scentralizowanej metody rejestracji schematów URL. Jako developer aplikacji możesz użyć niemal dowolnego schematu URL, konfigurując go w Xcode dla iOS lub dodając intent na Androidzie.

Nic nie stoi na przeszkodzie, by złośliwa aplikacja przejęła twój deep link, rejestrując się pod tym samym schematem i uzyskując dostęp do przesyłanych danych. Wysłanie czegoś jak app://products/1 nie jest szkodliwe, ale przesyłanie tokenów już stanowi zagrożenie bezpieczeństwa.

Gdy system operacyjny ma do wyboru dwie lub więcej aplikacji do otwarcia linku, Android wyświetli użytkownikowi okno dialogowe rozstrzygania niejednoznaczności (Disambiguation dialog). W przypadku iOS system dokonuje wyboru samodzielnie, więc użytkownik może nie być świadomy ryzyka. Apple podjął kroki zaradcze w nowszych wersjach iOS (od 11), wprowadzając zasadę "kto pierwszy, ten lepszy", jednak luka ta nadal może być wykorzystywana na inne sposoby, o których możesz przeczytać tutaj. Użycie uniwersalnych linków zapewnia bezpieczne linkowanie do treści w aplikacji na iOS.

OAuth2 i przekierowania

Protokół uwierzytelniania OAuth2 jest dziś niezwykle popularny, uznawany za najbardziej kompletny i bezpieczny. Na nim opiera się także protokół OpenID Connect. W OAuth2 użytkownik proszony jest o uwierzytelnienie przez stronę trzecią. Po pomyślnym zakończeniu, strona trzecia przekierowuje z powrotem do aplikacji żądającej z kodem weryfikacyjnym, który można wymienić na JWT — JSON Web Token. JWT to otwarty standard bezpiecznego przesyłania informacji między stronami w sieci.

W środowisku webowym to przekierowanie jest bezpieczne, ponieważ adresy URL w sieci są gwarantowane jako unikalne. W aplikacjach tak nie jest, bo jak wspomniano wcześniej, nie ma scentralizowanej metody rejestracji schematów URL! Aby rozwiązać ten problem bezpieczeństwa, należy dodać dodatkowe zabezpieczenie w postaci PKCE.

PKCE, wymawiane „Pixy”, oznacza Proof of Key Code Exchange i jest rozszerzeniem specyfikacji OAuth 2. Polega na dodaniu dodatkowej warstwy zabezpieczeń, która weryfikuje, czy żądania uwierzytelniania i wymiany tokenów pochodzą od tego samego klienta. PKCE wykorzystuje kryptograficzny algorytm skrótu SHA 256. SHA 256 tworzy unikalny „podpis” dla tekstu lub pliku dowolnej wielkości, ale:

  • Zawsze tej samej długości, niezależnie od rozmiaru pliku wejściowego

  • Gwarantuje zawsze ten sam wynik dla tych samych danych wejściowych

  • Jest jednokierunkowy (czyli nie można odwrócić procesu, aby odtworzyć oryginalne dane)

Teraz masz dwie wartości:

  • code_verifier - duży losowy ciąg znaków generowany przez klienta

  • code_challenge - wynik SHA 256 dla code_verifier

Podczas początkowej prośby /authorize klient wysyła również code_challenge dla code_verifier przechowywanego w pamięci. Po pomyślnym zakończeniu żądania autoryzacji klient wysyła code_verifier użyty do wygenerowania code_challenge. Dostawca tożsamości (IDP) oblicza wtedy code_challenge, sprawdza zgodność z wartością z pierwszego żądania /authorize i wysyła token dostępu tylko w przypadku zgodności wartości.

Gwarantuje to, że tylko aplikacja inicjująca proces autoryzacji może wymienić kod weryfikacyjny na token JWT. Nawet jeśli złośliwa aplikacja uzyska dostęp do kodu weryfikacyjnego, będzie on bezużyteczny bez dodatkowych elementów. Przykład działania można zobaczyć w tym przykładzie.

Biblioteką wartą rozważenia do natywnego OAuth jest react-native-app-auth. React-native-app-auth to SDK do komunikacji z dostawcami OAuth2, opakowujące natywne biblioteki AppAuth-iOS i AppAuth-Android z obsługą PKCE.

React-native-app-auth obsługuje PKCE tylko wtedy, gdy twój dostawca tożsamości (Identity Provider) również to umożliwia.

OAuth2 z PKCE

Bezpieczeństwo sieci

Twoje API powinny zawsze używać szyfrowania SSL. Chroni ono przed odczytaniem danych w postaci jawnej między serwerem a klientem. Bezpieczny punkt końcowy rozpoznasz po prefiksie https:// zamiast http://.

Przypinanie SSL (SSL Pinning)

Używanie punktów końcowych https nadal może narażać dane na przechwycenie. Przy https klient ufa serwerowi tylko z ważnym certyfikatem podpisanym przez zaufany urząd certyfikacji (CA). Atakujący może wykorzystać to, instalując złośliwy certyfikat główny CA na urządzeniu, co sprawi, że klient będzie ufał certyfikatom podpisanym przez atakującego. Poleganie wyłącznie na certyfikatach może narazić na atak man-in-the-middle.

Przypinanie SSL to technika kliencka zabezpieczająca przed tym atakiem. Działa poprzez osadzenie (przypięcie) listy zaufanych certyfikatów w kliencie podczas rozwoju. Tylko żądania podpisane zaufanym certyfikatem są akceptowane, a certyfikaty samopodpisane odrzucane.

Przy stosowaniu SSL pinningu pamiętaj o terminach ważności certyfikatów. Certyfikaty wygasają co 1-2 lata i po wygaśnięciu muszą zostać zaktualizowane zarówno w aplikacji, jak i na serwerze. Gdy certyfikat na serwerze zostanie zaktualizowany, aplikacje z osadzonym starym certyfikatem przestaną działać.

Podsumowanie

Nie istnieje niezawodny sposób zapewnienia bezpieczeństwa, ale dzięki świadomemu wysiłkowi i staranności można znacząco zmniejszyć prawdopodobieństwo wystąpienia naruszeń bezpieczeństwa w Twojej aplikacji. Inwestuj w środki bezpieczeństwa proporcjonalnie do wrażliwości przechowywanych danych, liczby użytkowników oraz szkód, jakie haker mógłby wyrządzić po uzyskaniu dostępu do ich kont. Pamiętaj: znacznie trudniej jest uzyskać dostęp do informacji, które nigdy nie zostały złożone w pierwszej kolejności.