Tmavý režim
Multi-brand přihlášení pro více instancí aplikace
Brand (vzhled loginu a e-mailů) se neposílá v callbacku ani v OIDC scope. Spravuje se centrálně v jabcore-platform-api a Login v2 ho dohledá podle OIDC klienta a přesného callbacku.
Brand ≠ user pool
Brand určuje, jak login vypadá. Které účty se smí přihlásit, určuje user pool aplikace — organizaci loginu bere Login v2 vždy z poolu, ne z brandu.
Datový model
Brand
Brand může mít zitadelOrgId — organizaci, jejíž auth e-maily (ověření, reset hesla) mají použít tento brand a jeho SMTP. Bez něj se pro auth e-maily použije brand user poolu organizace, případně výchozí brand (Jabcore).
zitadelOrgId brandu slouží i jako organizace samostatné přihlašovací domény (viz níže) — u klienta to bude organizace jeho user poolu.
Aplikace
Aplikace obsahuje:
oidcClientId— ID jejího OIDC klienta v ZITADELu;oidcApplicationType—webnebonative; typ se volí před prvním provisionem a u existující aplikace se při synchronizaci zachová podle ZITADELu;zitadelProjectIdazitadelApplicationId— vazbu na spravované vzdálené objekty;zitadelManaged— zda jejich životní cyklus vlastníjabcore-platform-api;- stav poslední synchronizace (
unconfigured,pending,synced,error); supportedBrandIds— seznam brandů, které aplikace smí používat;- libovolný počet instancí.
Seznam podporovaných brandů funguje stejně jako seznam podporovaných jazyků. Brand instance musí být v tomto seznamu.
Instance aplikace
Instance představuje jeden nasazený callback a obsahuje:
hostname, napříkladmanager.jabcore.cloudnebolocalhost:8083;- přesný
redirectUri, napříkladhttps://manager.jabcore.cloud/callback; - přesný
postLogoutRedirectUri, napříkladhttps://manager.jabcore.cloud/; - jeden
brandId.
Nativní aplikace mohou místo HTTP(S) použít vlastní URI schéma, například cz.jabcore.app:/oauth/callback. Hostname se u nich odvodí pouze pro zobrazení; brand resolver používá vždy přesnou Redirect URI.
Jeden host včetně portu nesmí být přiřazen různým brandům. Může mít více callback cest, pokud všechny používají stejný brand. Port je součást identity instance, takže například localhost:8081 a localhost:8083 mohou používat různé brandy.
Průběh loginu
text
Aplikace
│ client_id + obyčejný redirect_uri + openid profile email
▼
ZITADEL vytvoří auth request
▼
Login v2 načte clientId a redirectUri
▼
GET jabcore-platform-api/api/zitadel/login-brand
▼
aplikace → user pool → organizace loginu (vyhledání, registrace)
instance → brand → přihlašovací doména
▼
ověření účtu jen z organizace poolu → původní callback aplikaceVeřejný resolver vyžaduje clientId (aplikace) a přesný redirectUri (instance). Uživatel si proto nemůže query parametrem zvolit jiný brand ani organizaci.
Nastavení v admin panelu
1. Brand
V Brandy vytvoř brand klienta (logo, barvy, SMTP). ZITADEL Organization ID vyplň, jen když mají auth e-maily dané organizace používat tento brand (případně nastav brand přímo u user poolu).
2. Aplikace
V Aplikace otevři nebo vytvoř aplikaci a nastav:
- Podporované brandy — vyber všechny brandy, které může aplikace používat;
- změny ulož.
U nové aplikace se Client ID nevyplňuje. Po založení první instance vytvoří user-api automaticky ZITADEL projekt i OIDC aplikaci a získané Client ID uloží. Client ID se ručně zadává jen při jednorázovém připojení existující aplikace.
3. Instance
V detailu aplikace v části Instance aplikace přidej pro každý deployment:
- u webové aplikace hostname bez protokolu a cesty, ale včetně portu, pokud ho callback používá (u nativní aplikace se hostname nezadává);
- přesný OIDC callback;
- přesnou Post Logout URI;
- jeden z podporovaných brandů.
Příklad:
| Aplikace | OIDC client | Hostname | Redirect URI | Post Logout URI | Brand |
|---|---|---|---|---|---|
| OnlyBrain | 383916237421346819 | app.onlybrain.cz | https://app.onlybrain.cz/callback | https://app.onlybrain.cz/ | Jabcore |
| OnlyBrain | stejný | onlybrain.klient.cz | https://onlybrain.klient.cz/callback | https://onlybrain.klient.cz/ | Klient |
Přidání další instance nebo brandu nevyžaduje novou verzi Login v2 ani aplikace. V managed režimu stačí změna v administraci; synchronizace callback automaticky promítne do ZITADELu.
Automatická správa ZITADELu
jabcore-platform-api je zdroj pravdy. Jedné lokální aplikaci odpovídá právě jeden managed ZITADEL projekt a v něm právě jedna OIDC Web nebo Native aplikace. Projekt není sdílený mezi více lokálními aplikacemi. OIDC redirect URI jsou přesně odvozené ze všech aktuálních application_instances dané aplikace.
Managed projekty vlastní centrální platformní organizace nastavená přes ZITADEL_PROJECT_ORG_ID. V ZITADELu mají vypnuté vyžadování project role a project grantu; které účty se smí přihlásit, vynucuje Login v2 podle user poolu aplikace a přístup do produktu role a oprávnění jabcore-platform-api.
Synchronizace postupuje idempotentně:
- lokální požadovaný stav se uloží a označí jako
pending; - vytvoří se nebo ověří ZITADEL projekt;
- vytvoří se nebo ověří jedna OIDC Web/Native aplikace v tomto projektu;
- její Redirect URI a Post Logout URI se sjednotí s lokálními instancemi;
- uloží se vzdálená ID a
oidcClientId, smaže se stará chyba a stav přejde nasynceds časem vzitadelSyncedAt.
Pokud vzdálený krok selže, lokální požadovaný stav zůstane zachovaný, stav je error a detail je v zitadelSyncError. Opakovaný sync pokračuje bezpečně ze stejných ID a nesmí vytvářet další projekty nebo OIDC aplikace. Login používá poslední úspěšně synchronizované nastavení; změna instance se proto považuje za hotovou až ve stavu synced.
Převzetí existující aplikace
Aktuální ZITADEL konfiguraci není nutné vytvářet znovu. Do lokální aplikace se importují zitadelProjectId, zitadelApplicationId a oidcClientId, ověří se, že OIDC aplikace opravdu patří do daného projektu, a provede se první porovnání callbacků. Import začíná s zitadelManaged = false, takže smazání lokálního záznamu nikdy nesmaže převzatý projekt. Po kontrole lze vlastnictví explicitně přepnout na managed režim.
Mazání
Vzdálené objekty se kaskádově mažou jen pro zitadelManaged = true. Protože má managed projekt jedinou OIDC aplikaci, stačí úspěšně smazat projekt a ZITADEL smaže jeho aplikaci. Teprve potom se smaže lokální aplikace. Když ZITADEL mazání selže, lokální záznam se zachová se stavem error, aby nevznikl osiřelý projekt. U importovaných (zitadelManaged = false) aplikací se maže pouze lokální vazba.
Callbacky v ZITADELu
V ZITADELu musí být jednou registrovaný každý skutečně používaný callback. Callback se už neduplikuje pro různá organization ID. V managed režimu tento seznam neupravuj ručně: synchronizace ho vždy vrátí do stavu definovaného lokálními instancemi.
Totéž platí pro Post Logout URIs: hodnota se už neodvozuje z originu callbacku, ale nastavuje se explicitně u každé instance. Při aktualizaci existujícího nativního klienta synchronizace znovu pošle OIDC_APP_TYPE_NATIVE, takže ho nepřepne na Web.
Správně:
text
http://localhost:8080/callback
https://manager.jabcore.cloud/callback
https://manager.test.jabcore.cloud/callbackNepoužívat:
text
/callback?brand_org_id=377696186024394755
/callback?brand_org_id=377963078832095235Lokální HTTP callback vyžaduje u OIDC aplikace zapnutý Development Mode.
Konfigurace klientské aplikace
Frontend potřebuje už jen běžné OIDC hodnoty:
env
APP_ZITADEL_AUTHORITY=https://zitadel.auth.jabcore.cloud/
APP_ZITADEL_CLIENT_ID=<OIDC_CLIENT_ID>
APP_ZITADEL_REDIRECT_URI=http://localhost:8080/callback
APP_ZITADEL_POST_LOGOUT_URI=http://localhost:8080APP_ZITADEL_ORG_ID se nepoužívá. Scopes zůstávají:
text
openid profile emailProč nepoužívat organization scope
Scope urn:zitadel:iam:org:id:<ORG_ID> u registrované aplikace nic nemění — organizaci loginu určuje user pool aplikace a scope se ignoruje. Klienti proto posílají jen openid profile email.
API
Admin CRUD:
GET /api/applications/:appCode/instancesPOST /api/applications/:appCode/instancesPUT /api/applications/:appCode/instances/:instanceIdDELETE /api/applications/:appCode/instances/:instanceIdGET /api/applications/:appCode/zitadel/statePOST /api/applications/:appCode/zitadel/syncPOST /api/applications/:appCode/zitadel/detach
Veřejný serverový resolver pro Login v2:
text
GET /api/zitadel/login-brand?clientId=<CLIENT_ID>&redirectUri=<URI>Endpoint vrací pouze identifikaci aplikace, instance a brandu; SMTP hesla ani jiná tajemství nezpřístupňuje.
Pořadí nasazení
- Nová databáze:
sql/init.sql(schéma včetně instancí, synchronizace a user poolů), potom seedy (sql/panel_auth_permissions.sql,sql/auth_panel_notifications.sql,sql/data.sql). - U user poolů zkontroluj propojení se Zitadel organizací.
- U aplikace nastav pool, podporované brandy a instance a spusť synchronizaci.
- Ověř stav
synceda výsledné callbacky. - Nasaď Login v2 s nastaveným
USER_API_URL. - Klienti používají obyčejný callback a scopes
openid profile email.
Diagnostika
- Špatný nebo výchozí brand: zkontroluj
oidcClientId, přesnou hodnoturedirectUri, přiřazený brand a jehozitadelOrgId. - Po loginu „Spravovat profil“: zkontroluj, že scope neobsahuje
urn:zitadel:iam:org:id:*a klient používá obyčejný callback. invalid_request: callback klienta se přesně neshoduje s callbackem registrovaným v ZITADELu.- Resolver vrací 404: dvojice
clientId + redirectUrinení v instancích aplikace nastavena. - Synchronizace je
error: zkontrolujzitadelSyncError, dostupnost ZITADELu a oprávnění service accountu; po opravě spusť sync znovu.
Unikátnost e-mailu
E-mail je unikátní v rámci user poolu (organizace). Stejná adresa ve dvou poolech jsou dva různé účty — viz User pooly. Login hledá účty jen v organizaci poolu aplikace.
Přihlašovací doména brandu
Brand může mít loginDomain a loginDomainVerifiedAt. Resolver vrací doménu pouze při aktivaci; OIDC klient, issuer ani mapování instancí se tím nemění. LoginV2 přesměruje původní požadavek na aktivní doménu před přihlášením. Samotná centrální OIDC přesměrování mohou být krátce viditelná.
Správa v panelu používá samostatné endpointy chráněné brands.manage:
PUT /api/admin/brands/:id/login-domains{ "domain": "login.partner.cz" };nulldoménu odstraní. Změna ruší aktivaci.POST /api/admin/brands/:id/login-domain/activateověří DNS/HTTPS/propojení, zaregistruje ZITADEL Trusted Domain a aktivuje směrování.POST /api/admin/brands/:id/login-domain/deactivatevrátí nové toky na centrální login.
Caddy využívá veřejný indexovaný lookup GET /api/zitadel/login-domain-allowed?domain=… pro on-demand TLS; active=true navíc vyžaduje aktivaci pro obsluhu loginu. Neznámá doména vrací 403, povolená 204. Čekající doména musí být pro TLS povolena, jinak nelze ověřit její HTTPS před aktivací.
Jednorázově začlenit deploy/Caddyfile.login-domains do Caddyfile. Konkrétní partnerské hostname se pak zadávají pouze v panelu. DNS musí vlastník domény nasměrovat na proxy. Podrobný postup a omezení jsou v sousedním repozitáři jabcore-platform-admin/docs/partner-login-domains.md.
