Niedawno zmagałem się z problemem bezpieczeństwa i uwierzytelniania użytkownika dla aplikacji na iOS, którą robię, głównym problemem jest to, jak można zezwolić użytkownikom na rejestrację za pomocą dowolnej usługi zewnętrznej (lub rodzime konto użytkownika) i nadal utrzymują bezpieczny i modułowy proces.Projekt dla uwierzytelniania mobilnego z serwerem NodeJS
Rozwiązanie, które wymyśliłem, jest dość złożone i nie jestem w 100% pewien, czy to wszystko jest najlepszą praktyką, więc pomyślałem, że zapytam i otrzymam sugestie i wskazówki na temat tego, co mogę naprawić, co działa dobrze , co złe itp.
Po pierwsze jest kwestia uwierzytelnienia. Lubię oddzielić ideę uwierzytelniania od idei użytkowników. Dla mnie uwierzytelnianie to coś, co jest wykonywane przez urządzenie lub klienta, niezależnie od konkretnego użytkownika, a konto użytkownika jest czymś, co zostało utworzone lub pobrane w wyniku tego uwierzytelnienia. To, co pozwala na to, to traktowanie uwierzytelnienia klienta jako jednego procesu, a następnie uwierzytelnianie użytkownika (sprawdzanie, czy konto istnieje itp.), Tak aby istniały dwie warstwy zabezpieczeń. Powiedzmy na przykład, że klient pomyślnie uwierzytelnia się, ale wtedy hasło użytkownika jest błędne, ogólne uwierzytelnienie zakończyłoby się niepowodzeniem i posiadanie dwóch luźno powiązanych pojęć jest w ten sposób korzystne.
Aby zaimplementować uwierzytelnianie, użyłem JWT (JSON Web Tokens) zamiast plików cookie z kilku powodów. 1) Działają one znacznie lepiej z urządzeniami mobilnymi 2) są mniej sesyjne, co znacznie ułatwia implementację serwera i nie podlega atakom CORS, o ile wiem. JWT wydaje się lepszym rozwiązaniem podczas pracy z urządzeniami mobilnymi. Użyłem wielu bibliotek npm, w szczególności express-jwt i jsonwebtoken do uwierzytelniania po stronie serwera.
Jak już wspomniałem powyżej, nie tylko starałem się wykonywać uwierzytelnianie, ale także pozwoliłem użytkownikom na rejestrację za pomocą dowolnej usługi zewnętrznej, takiej jak Facebook, Twitter, w celu zmniejszenia tarcia użytkownika podczas rejestracji. Po dłuższej chwili zastanowienia się i szukaniu w Google, wpadłem na pomysł dostawców tożsamości, systemu uwierzytelniania, w którym każdy "typ konta" jest traktowany jako osobny dostawca tożsamości i generalizowany w celu dostarczania informacji, takich jak access_token. , user_id, data wygaśnięcia itp. Dostawcy tożsamości są bardzo podobni do "połączonych kont", które widzisz na wielu stronach ustawień aplikacji. Na stronie iOS rzeczy, zrobiłem klasę abstrakcyjną i dla każdej usługi chcę wspierać, zrobiłem konkretną podklasę, FacebookIdentityProvider
, LocalIdentityProvider
(e-mail/hasło) itd
na stronie serwera, kiedyś Passport modułów aby powrócić do każdego rodzaju dostawcy tożsamości. Na przykład mają moduł facebook-token, jeden dla adresu e-mail i hasła użytkownika itp. Tak więc utworzyłem jedną trasę api /authenticate
, którą moi klienci wysyłają do dostawcy usług z numerem seryjnym i na podstawie ciągu identyfikatora, local
, facebook-token
, paszport nazwałaby odpowiedni moduł do uwierzytelnienia tego dostawcy na podstawie dostarczonych informacji.
Ogólnie przepływ bezpieczeństwo wygląda następująco:
- Klient sprawdza dysk do poprzedniego tokenu JWT (przechowywany bezpiecznie używając Lockbox).
- Jeśli zostanie znaleziony token, klient wysyła żądanie do mojego punktu końcowego
verify
. Ten punkt końcowy sprawdzi, czy token jest nadal ważny i nie wygasł. - Jeśli token nie wygasł, klient otrzymuje 200 i wszystko jest dobre ze światem. Jeśli nie, wówczas klient wyśle żądanie do mojego punktu końcowego
refresh_token
z wygasłym tokenem, który spróbuje ponownie wystawić token. Jeśli to się nie powiedzie, klient zgłasza żądanie do mojego punktu końcowegoauthenticate
, który może zostać wywołany tylko w wyniku działania użytkownika. - Jeśli na dysku nie ma oryginalnego tokena, to samo dzieje się na końcu 3, klient musi czekać na uwierzytelnienie użytkownika.
Po wykonaniu wszystkich tych czynności nadal jestem trochę nieostrożny w kilku kwestiach. Przede wszystkim czytałem coś na stronie express-jwt dotyczącej odwoływania tokenów. Co decyduje o tym, kiedy powinienem odwołać token i ponownie zalogować użytkownika? Nie ma sensu utrzymywać odświeżania tokena za każdym razem, gdy wygasa on w nieskończoność.
Po drugie, kiedy wysyłam dostawcę zserializowanego identyfikatora do serwera, przekazuję słownik dodatkowych informacji, które będą używane przez paszport do uwierzytelniania na podstawie procesu. Jeśli się powiedzie, zostanie utworzony dostawca tożsamości dla tego użytkownika i zapisany w bazie danych. Czy to wystarczy, czy też powinienem zrobić więcej dzięki polu access_token i innym polom, które otrzymuję z udanego połączenia? Szczególnie w przypadku pakietu SDK Facebooka otrzymuję token dostępu, gdy klient uwierzytelnia się za pośrednictwem aplikacji, a następnie inny token, gdy klient ponownie uwierzytelnia się z serwerem.
Dodatkowym pomysłem było dla kogoś zintegrowanie klucza API, który został przekazany z każdym żądaniem za pomocą parametru nagłówka lub zapytania. Klucz API będzie utrzymywany w tajemnicy i zabezpieczony po stronie klienta. To, co myślę, że to mogłoby zrobić, to dodać kolejną warstwę "uwierzytelniania" nawet do klientów, którzy jeszcze nie przeszli procesu uwierzytelniania. Tylko klienci z kluczem API będą w stanie w ogóle osiągnąć mój api i tylko ci klienci będą mogli próbować uwierzytelnienia.
Moje wykształcenie jest formalnie związane z bezpieczeństwem w cyberprzestrzeni (nigdy nie byłem dobry), a teraz mam rozbudowany mobilny program rozwoju, więc lepiej rozumiem te rzeczy niż większość, ale czuję się, jakbym nie dopuścił się potencjalnie niebezpiecznych dziur. Niestety nie mogę opublikować kodu, ponieważ jest to dla mojej firmy, ale jeśli jest coś, czego nie wyjaśniłem, po prostu komentuj i chętnie omówię sprawę.
Ponadto uważam, że powinienem wspomnieć, wszystko to odbywa się za pośrednictwem protokołu SSL, który skonfigurowałem przy użyciu Nginx, a wszystkie moje żądania sieci iOS są wykonane przy użyciu Overcoat. Ostatecznie chcę używać Nginx jako load balancera, ale jest to post na inny dzień.
To nie jest miejsce dla StackOverflow. – fuzz
Więc gdzie to jest? Mówienie, że nie należy, nie jest pomocne. – barndog
Jeśli jest to związane z koncepcjami programowania wyższego poziomu lub jest konceptualne (ale wciąż związane z programowaniem), powinno być na http://programmers.stackexchange.com/ – fuzz