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ć
| Powierzchnia | Gdzie odpowiada | Kto może ją wołać |
|---|---|---|
| OAuth 2.1 / OpenID Connect | /oauth/, /.well-known/ na hoście issuera | Każdy. To powierzchnia, która wydaje dane uwierzytelniające. |
| Hostowane strony logowania | cała reszta na hoście issuera | Twoi użytkownicy, w przeglądarce. |
| API zarządzania | /api/v1/ na hoście zarządzania | Uwierzytelnieni, 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
| Magazyn | Co trzyma |
|---|---|
| PostgreSQL | Źródło prawdy. Konta, aplikacje, środowiska, użytkownicy, przestrzenie robocze, członkostwa, role, klienci, klucze i dziennik zdarzeń. |
| Redis | Cache, liczniki limitów zapytań i wyniki zadań w tle — wszystko, co da się odtworzyć albo czemu wolno wygasnąć. |
| RabbitMQ | Kolejka 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.