Tmavý režim
JWT Provideři
Konfigurace
JWT provideři se konfigurují v config/default.yaml (nebo production.yaml) pod klíčem token.jwt:
yaml
token:
keyRefreshIntervalMinutes: 60 # Interval refreshe JWKS klíčů
jwt:
- provider: zitadel
baseUrl: http://zitadel:8080
jwk: /oauth/v2/keys
issuer: http://localhost:8080
hostOverride: localhost # Volitelné: přepíše host při fetch klíčůJediným providerem Jabcore Platform je Zitadel. API přijme jen token účtu, který patří do některého user poolu — pool se určuje podle organizace, která uživatele vlastní. Token jiného providera by žádný pool neměl, a API ho proto odmítne (401).
Google / Microsoft přihlášení
Google a Microsoft nejsou samostatní JWT provideři. Federovaný login přes tyto účty se konfiguruje uvnitř Zitadelu jako externí Identity Provider (IdP). Token, který nakonec dorazí na API, vždy podepisuje Zitadel.
Zitadel (primární provider)
Zitadel je self-hosted OIDC provider, který zajišťuje autentizaci pro všechny aplikace Jabcore.
Speciální claims
Zitadel přidává do JWT tokenu navíc dynamické claims, kde je část identifikátoru zakódovaná přímo v názvu klíče:
| Vzor claimu | Popis |
|---|---|
urn:zitadel:iam:org:id:{orgId} | Organizace kontextu přihlášení — z názvu klíče se extrahuje {orgId} |
urn:zitadel:iam:user:resourceowner:id | Organizace, která vlastní uživatele = jeho user pool. Pokud ho klient nevyžádá (scope urn:zitadel:iam:user:resourceowner), API organizaci dohledá v Zitadelu podle sub a výsledek cachuje. |
urn:zitadel:iam:org:project:{projectId}:roles | Role uživatele v daném Zitadel projektu |
Extrakce se provádí regexem v JWTService — orgId i projectId nejsou hodnoty, ale součást názvu claimu.
Organizace a user pool
Každý účet patří do organizace svého user poolu. Autorizační middleware z tokenu odvodí pool (req.token.userPoolId, userPoolCode, isPlatformPool) a účet dál identifikuje dvojicí pool + e-mail. Token je odmítnut, když:
- organizace uživatele nepatří žádnému poolu,
client_idtokenu patří aplikaci z jiného poolu, než je pool účtu.
Login flow (PKCE)
Dev-only endpoint
Endpointy /api/zitadel/login a /api/zitadel/callback jsou určené pro lokální vývoj a testování. Callback vrací tokeny jako JSON odpověď (nikoliv redirect na frontend). V produkci provádí OIDC flow přímo frontend aplikace přes Zitadel.
1. GET /api/zitadel/login?client_id=<clientId>
→ Redirect na Zitadel login stránku (s PKCE)
2. Uživatel se přihlásí v Zitadel
3. GET /api/zitadel/callback?code=<authCode>&state=<state>
→ Výměna auth code za tokeny
→ JSON response: { access_token, id_token, id_token_claims, ... }Webhoky
Zitadel posílá webhooky při událostech uživatele:
| Událost | Akce v API |
|---|---|
user.created | Vytvoří uživatele v DB (pokud neexistuje) |
user.deactivated | Deaktivuje uživatele (active = false) |
user.reactivated | Aktivuje uživatele (active = true) |
user.removed | Deaktivuje uživatele (active = false) — záznam se zachovává pro audit trail |
Webhook endpoint: POST /api/zitadel/webhook
Autentizace: X-Webhook-Secret header
Tělo webhooku
json
{
"eventType": "user.created",
"orgId": "org_abc123",
"user": {
"email": "novy.uzivatel@firma.cz",
"firstName": "Jan",
"lastName": "Novák"
}
}Google / Microsoft (federovaní přes Zitadel)
Login přes Google nebo Microsoft není konfigurován v Jabcore Platform API. Tito poskytovatelé jsou nastaveni jako externí Identity Provideři uvnitř Zitadelu (Console → Default Settings → Identity Providers). Při přihlášení:
- Uživatel klikne v Zitadel login UI na "Sign in with Google/Microsoft"
- Zitadel provede OAuth flow proti Google/Microsoftu
- Zitadel vytvoří/aktualizuje lokálního uživatele a vydá vlastní JWT
- API přijme Zitadel JWT a verifikuje ho proti Zitadel JWKS
Z pohledu API tedy existuje vždy jediný provider — Zitadel. Účet vytvořený přes Google/Microsoft vzniká v organizaci user poolu aplikace, přes kterou se uživatel poprvé přihlásil; v jiném poolu je to jiný účet. upn claim zůstává v emailové extrakci pro případy, kdy Zitadel propaguje původní Microsoft UPN do tokenu.
Jak probíhá verifikace
typescript
// JWTService — zjednodušeně
async verifyToken(token: string): Promise<DecodedToken> {
for (const provider of this.providers) {
try {
const decoded = jwt.verify(token, provider.keys, {
algorithms: ['RS256', 'RS384', 'RS512'],
issuer: provider.issuer,
});
return { ...decoded, provider: provider.name };
} catch {
// Zkusí další provider
}
}
throw new ApiException(401, 'Invalid token');
}Tokenem projdou všichni provideři v pořadí konfigurace. První úspěšná verifikace vyhrává.
Troubleshooting
Token is invalid
- Ověřte, že
issuerv konfiguraci odpovídáissclaimu v tokenu - Ověřte, že JWKS endpoint je dostupný ze serveru
- Zkontrolujte expiraci tokenu (
expclaim)
JWKS fetch failed
- Zkontrolujte dostupnost
baseUrl + jwkendpointu - Pro Zitadel v Dockeru: ujistěte se, že
zitadelhostname je resolvovatelný - Použijte
hostOverridepokud se liší interní a externí hostname
Email extraction failed
- Zkontrolujte, které claims váš provider vydává
- Přidejte logiku extrakce do
JWTService.extractEmail()pokud používáte nestandartní claim
