typescript and javascript logo
author avatar

Grzegorz Dubiel

23-04-2025

Jeden użytkownik, wiele usług: łączenie Dropboxa z Google w NestJS

W erze, w której mamy mnóstwo usług do wszystkiego, często pojawia się potrzeba ich automatyzacji lub uzyskania jasnego wglądu w zasoby przechowywane w tych usługach. Z perspektywy dewelopera istnieje potrzeba tworzenia abstrakcji w kodzie, które pozwalają użytkownikom łączyć te usługi z ich kontem w aplikacji którą budujesz. Na szczęście mamy ugruntowany standard jakim jest OAuth. Przejdźmy do przykładu i zbudujmy funkcję, która pozwala na połączenie konta Dropbox z kontem utworzonym za pomocą Google.

Wprowadzenie

Zbudujemy ten przykład na bazie projektu, który już implementuje proces uwierzytelnienia Google. Opisałem ten przypadek w poprzednim poście, który stanowi kontynuację obecnego artykułu. Aby zacząć, możesz sklonować to repozytorium i przełączyć się na branch o nazwie only-google-auth.

Naszym celem jest umożliwienie użytkownikom połączenia wielu kont Dropbox. Aby to osiągnąć, musimy przygotować:

  • OAuth dla Dropboxa
  • Aktualizację bazy danych w celu przechowywania offline credentials (tokenów) Dropboxa
  • Aktualizację kontrolerów, aby umożliwić użytkownikom inicjowanie procesu OAuth dla Dropboxa

Po zaimplementowaniu patentu łączenia kont będziemy mieć podstawy pod serwis, który może obsługiwać dodatkowe funkcje — np. zbieranie i organizowanie plików z Dropboxa i Google Drive, umożliwienie agentom AI dostępu do zasobów użytkownika przechowywanych w obu usługach. Alternatywnie, może działać jako usługa uwierzytelniania/proxy dla innej aplikacji, która wymaga połączenia kont.

Szyfrowanie

Będziemy przechowywać access i refresh tokeny zwrócone przez usługę uwierzytelnienia Dropboxa w bazie danych, aby później móc pobierać zasoby użytkowników w ich imieniu. Aby upewnić się, że dane uwierzytelniające są bezpiecznie przechowywane w naszej bazie danych, musimy je zaszyfrować. Aby to zrobić, stworzymy prosty moduł EncryptionModule, który zawiera serwis EncryptionService.

typescript

Używamy AES-256-GCM do szyfrowania symetrycznego. Pamiętaj, aby dodać ENCRYPTION_KEY do swojego pliku .env.

Dodanie zmian do schemy bazy danych

Musimy zaktualizować naszą schemę bazy danych, aby przechowywać dane Dropboxa wraz z relacjami. Aplikacja używa Drizzle do obsługi operacji na bazie danych, więc to zadanie będzie bezproblemowe. Stworzymy dwie nowe tabele:

  • dropbox_accounts do przechowywania credentials Dropboxa wraz z ID konta Dropbox.

typescript

  • users_to_dropbox_accounts do utrzymywania relacji many-to-many pomiędzy tabelami users i dropbox_accounts.

typescript

Dodatkowo dodajemy kolumnę: is_connected_to_dropbox do tabeli users, do oznaczenia użytkowników, którzy mają podłączone swoje konta Dropbox.

Wybór relacji many-to-many między tabelami users i dropbox_accounts pozwala naszym użytkownikom na połączenie wielu kont Dropbox z ich kontami w naszej aplikacji. Takie podejście jest elastyczne i skalowalne.

Dropbox OAuth

Aby połączyć konto Dropbox z kontem utworzonym za pomocą Google, musimy zapewnić użytkownikom sposób na uwierzytelnienie ich konta Dropbox. Proces ten będzie niemal identyczny z typowym OAuth. Musimy wysłać ID użytkownika, do usługi uwierzytelniania Dropbox i otrzymać go z powrotem. Aby to obsłużyć, możemy przekazać token JWT z userId w payload do parametru state w authorizaion URL.

Aby przekazać ID użytkownika do parametru state, musimy udostępnić go w kontekście naszego uwierzytelniania Dropbox. Aby to osiągnąć, musimy wyodrębnić obiekt użytkownika z execution context i przekazać ID użytkownika jako stan. Możemy to osiągnąć w DropboxGuard.

typescript

Gdy mamy już userId zwrócone z guarda, możemy go wykorzystać w DropboxStrategy, czyli strategii uwierzytelniania opartej na Passport.js.

Zainsatlujmy passport-dropbox-oauth2:

typescript

oraz zdefinujmy strategię:

typescript

Teraz wyraźnie widzimy w którym miejscu wysyłamy state a w którym dostajemy go z powrotem.

Aby przesłać parametr state, pobieramy userId (zwrócony przez guarda) z kontekstu, a następnie tworzymy JWT token z krótką datą ważności, który zawiera userId.

typescript

Następnie dołączamy token do parametru state. który jest częścią authorization URL, który generowany jest dla dla użytkownika po wywołaniu endpointa auth/dropbox.

Parametr state będzie dostępny, gdy serwis uwierzytelniający przekieruje użytkownika do naszego endpointa auth/dropbox/callback. Aby uzyskać dostęp do state w środku metody validate która pochodzi z Dropbox auth strtegy i jest wywoływana w auth callback'u, musimy przekazać flagę passReqToCallback: true w obiekcie konfiguracyjnym konstruktora. Wewnątrz wspomnianej metody validate możemy odczytać parametr state i wyciągnąć ID użytkownika z tokena JWT używając metody validateStateToken z klasy AuthService.

typescript

Po walidacji dane konta Dropbox zostaną przekazane do callback'u done, co umożliwi ich dalsze przetwarzanie.

Dodanie kontrolerów

Teraz dodamy logikę uwierzytelniania Dropbox do modułu controllers.

Główna logika uwierzytelniania i token flow jest już zaimplementowana, więc musimy tylko podłączyć do niej naszą logikę uwierzytelnienia z Dropbox'em.

Punkt startu wygląda tak:

typescript

Pierwszą rzeczą, którą musimy dodać, jest endpoint do rozpoczęcia uwierzytelniania oraz middleware, które pełni rolę strażnika, który zapewnia, że tylko uwierzytelnieni użytkownicy mogą podłączyć konoto Dropboxa do swoich istniejących kont w aplikacji.

typescript

Bardzo ważną rzeczą jest to, że łączymy JwtAuthGuard i DropboxGuard, przekazując je do @UseGuards. Guards pełnią rolę warstwy autoryzacji, przekazanie ich jeden po drugim mówi NestJS, aby najpierw wykonał kod odpowiedzialny za sprawdzenie dostępu użytkownika do aplikacji, a następnie aktywował uwierzytelnianie Dropbox.

Doatkowo DropboxGuard pobiera userId z execution context i przekazuje go do DropboxStrategy,w sposób jaki omówiliśmy w poprzedniej sekcji tego artykułu.

Po wstępnym uwierzytelnieniu, IdP Dropbox'a przekieruje użytkownika do endpointa który jest naszym auth callback'iem dla Dropbox'a.

typescript

W tym momencie ponownie używamy DropboxGuard, aby aktywować metodę validate pochodzącą z DropboxStrategy. Mając wszystkie dane wymagane do połączenia, możemy uruchomić logikę łączenia konta Dropbox, obsługiwaną przez AuthService.

Logika łączenia kont

Ostatnie kroki które musimy podjąć to:

  • walidacja danych
  • uaktualnienie powiązanych rekordów w bazie danych

Musimy dodać następującą logikę do naszego istniejącego AuthService:

typescript

Najpierw musimy sprawdzić, czy otrzymane dane pasują do naszej schem'y, sparsować je i wywnioskować typ. Do tego zadania doskonale nadaje się zod. Jeśli coś jest nie tak z danymi, powinniśmy od razu wyrzucić błąd. Kolejnym krokiem jest sprawdzenie, czy userId odpowiada ID docelowego użytkownika, które jest już zapisane w naszej bazie danych ( userId pochodzi z parametru state). Jeśli nie, powinniśmy także wyrzuić błąd.

Gdy wszytkie sprawdzenia przejdą pomyślnie, możemy uaktualnić rekordy w bazie danych aby odzwierciedlały połączenie kont.

typescript

Używamy naszego EncryptionService, stworzonego wcześniej, do szyfrowania tokenów Dropbox'a. Jeśli chodzi o tabelę dropbox_accounts, to aktualizujemy bazę danych za pomocą paternu upsert (ON CONFLICT DO UPDATE ...) w przypadku ponownego połączenia, np. gdy refresh token był przeterminowany, a użytkownik ponownie się połączył. Dodatkowo dobrze jest opakować wszystkie zapytania w transakcję, aby zapewnić spójność oprecji. Jeśli wystąpią jakiekolwiek błędy podczas wykonywania zapytań, cała transakcja zostanie wycofana do stanu sprzed transakcji.

Wnioski

Wróciliśmy do NestJS, aby dodać nową funkcję, i okazało się to zaskakująco bezproblemowe. Bezpiecznie dodaliśmy nową strategię uwierzytelnienia, traktując to jak dodawanie kolejnej cegiełki. Nawet w tak skomplikowanym przypadku udało nam się rozbić proces uwierzytelnienia na mniejsze, łatwe do zarządzania kroki. Moglibyśmy nawet połączyć więcej kont z innych serwisów w podobny sposób, uzyskując dostęp do większej ilości zasobów użytkowników rozproszonych pomiędzy różnymi usługami. Moim zdaniem NestJS to technologia, którą warto znać, jeśli interesujesz się rozwojem aplikacji webowych w JavaScript. Najlepszym sposobem na przyswojenie framweworka lub biblioteki i naukę nowych rzeczy jest budowanie. Więc sklonuj repozytorium, dodaj kilka funkcji (np. pobieranie zasobów użytkowników z Google i Dropbox lub obsługę odświeżania tokenów Dropbox) i NestJS przestanie być dla Ciebie skomplikowany (jeśli kiedykolwiek był).

Dzięki za wspólne zgłebienie tego tematu 🙌

typescript and javascript logogreg@aboutjs.dev

©Grzegorz Dubiel | 2026