Przejdź do treści

Jak zbudowana jest Kleora

Kształt usługi hostowanej — jeden adres na środowisko, dwie powierzchnie API i miejsce, w którym leży stan. Oraz dlaczego hostowania u siebie dzisiaj nie wspieramy.

Jeden adres na środowisko

Każde środowisko każdej aplikacji ma własny host issuera: https://acme.kleora.eu dla produkcji i https://acme.sandbox.kleora.eu dla sandboksa. To nie jest kosmetyka. Issuer w OpenID Connect jest identyfikowany adresem URL, więc danie każdemu środowisku własnego oznacza, że discovery, zestawy kluczy, tokeny i sesje rozdziela sam protokół, a nie sprawdzenie gdzieś w naszym kodzie.

Żądanie przychodzące na taki host zostaje przypisane do dokładnie jednego środowiska, zanim cokolwiek zostanie wyrutowane. Nieznany host to 404, nigdy odesłanie gdzie indziej.

API zarządzania odpowiada na innym hoście — api.kleora.io — i tylko tam.

Dwie powierzchnie i to, czym każda z nich może być

PowierzchniaGdzie odpowiadaKto może ją wołać
OAuth 2.1 / OpenID Connect/oauth/, /.well-known/ na hoście issueraKażdy. To powierzchnia, która wydaje dane uwierzytelniające.
Hostowane strony logowaniacała reszta na hoście issueraTwoi użytkownicy, w przeglądarce.
API zarządzania/api/v1/ na hoście zarządzaniaUwierzytelnieni, w zasięgu jednego środowiska albo konta.

Podział wymusza host, na który przyszło żądanie: API zarządzania nie istnieje na hoście issuera, a powierzchnia OAuth nie istnieje na hoście zarządzania. Nie „jest odrzucane” — trasy nie są tam w ogóle zamontowane, więc odpowiedzią jest 404 z konstrukcji. W kodzie obie mieszkają też w osobnych pakietach, więc pytanie „czy to jest osiągalne bez danych uwierzytelniających?” zawsze ma odpowiedź celową, a nie przypadkową.

Przed obiema stoi serwer brzegowy. Dla hosta issuera decyduje po ścieżce: endpointy OAuth i przepływu idą do aplikacji, a cała reszta serwowana jest z paczki stron logowania. Nagłówek Host przekazuje nietknięty — i to właśnie pozwala jednemu wdrożeniu odpowiadać na issuery wszystkich klientów bez ich listy gdziekolwiek.

Strony logowania to statyczna aplikacja

Strony, które widzą Twoi użytkownicy — logowanie, rejestracja, potwierdzenie adresu, reset hasła, wybór przestrzeni roboczej — to statyczna aplikacja jednostronicowa serwowana na hoście issuera Twojej aplikacji. Rozmawia wyłącznie z /flow/* w tym samym origin, a to jest API w JSON; serwer nie renderuje żadnego HTML-a.

Dwie konsekwencje warte zapamiętania. Twój branding jest danymi, nie szablonem: strona pobiera rekord brandingu jako JSON i go stosuje, dlatego zmiana koloru działa bez wdrożenia po którejkolwiek ze stron. I logowanie wymaga JavaScriptu.

Zakończenie przepływu to zawsze nawigacja najwyższego poziomu z powrotem do /oauth/authorize/continue, więc kod autoryzacyjny trafia wyłącznie do odpowiedzi nawigacyjnej — nigdy do treści JSON, którą mógłby odczytać skrypt.

Gdzie leży stan

MagazynCo trzyma
PostgreSQLŹródło prawdy. Konta, aplikacje, środowiska, użytkownicy, przestrzenie robocze, członkostwa, role, klienci, klucze i dziennik zdarzeń.
RedisCache, liczniki limitów zapytań i wyniki zadań w tle — wszystko, co da się odtworzyć albo czemu wolno wygasnąć.
RabbitMQKolejka pracy, która nie może zginąć między zleceniem a wykonaniem: przede wszystkim wysyłka e-maili.

Każdy wiersz należący do środowiska niesie tożsamość tego środowiska, a dostęp do danych jest z konstrukcji ograniczony do jednego środowiska — zapytanie bez środowiska to błąd programisty, który kończy się wyjątkiem, a nie cudzymi wierszami.

Tokeny dostępu są bezstanowe: to podpisane JWT i weryfikacja nie zagląda nigdzie. Tokeny odświeżania, sesje i kody autoryzacyjne są odwrotnością — to wiersze w PostgreSQL, więc dla nich unieważnienie działa natychmiast.

Co to znaczy dla Twojej aplikacji

  • Weryfikacja nie kosztuje Cię żadnego żądania do nas. Twoje API pobiera zestaw kluczy raz, trzyma go tak długo, jak mówi odpowiedź, i sprawdza podpisy lokalnie. Role i uprawnienia są w tokenie.
  • Rotacja klucza nie wymaga skoordynowanego wdrożenia. Kolejny klucz podpisujący trafia do zestawu, zanim zostanie użyty, więc kid świeżo podpisanego tokenu jest już w Twoim cache'u.
  • Obie powierzchnie nie są jeszcze od siebie odizolowane. Dziś to jeden artefakt wdrożeniowy, więc nie da się ich skalować ani ograniczać niezależnie. Granica w kodzie jest prawdziwa, granica wdrożeniowa to praca na przyszłość — i wolimy to powiedzieć, niż sugerować izolację, której nie dostajesz.

Kontrakty, na których możesz budować

  • {issuer}/.well-known/openid-configuration — discovery, a z niego zestaw kluczy. Wszystko, czego potrzebuje dowolny klient OpenID Connect; nasze SDK są udogodnieniem, nie wymogiem.
  • {issuer}/oauth/openapi.json — dokument OpenAPI powierzchni OAuth.
  • https://api.kleora.io/api/v1/openapi.json — to samo dla API zarządzania, i dokument, z którego generowane są nasze własne klienty.

Przepływem przeglądarkowym jest authorization code z PKCE; implicit i password grant nie są oferowane.

Dlaczego nie ma opowieści o hostowaniu u siebie

Musiałyby być spełnione trzy rzeczy i nie jest spełniona żadna. Nie ma granicy wdrożeniowej między dwiema powierzchniami API, więc „uruchom publiczną połowę w swojej strefie DMZ” nie jest konfiguracją — jest zmianą architektury. Nie ma opublikowanych obrazów ani niczego wersjonowanego do uruchomienia. I nie ma wspieranej konfiguracji: istniejące ustawienia zakładają nasz własny kontekst operacyjny, a my nigdy nie uruchamialiśmy tego nigdzie indziej.

Wolimy opublikować tę stronę niż instrukcję wdrożenia, która po cichu nie działa. Jeśli to się zmieni, stanie się tak dlatego, że będzie co uruchamiać — i wtedy powie to właśnie ta strona.