Keycloak Runbook
Keycloak Runbook
Section titled “Keycloak Runbook”Keycloak je komplexní systém. Tato příručka pomáhá řešit nejčastější problémy.
Authentication & JWT
Section titled “Authentication & JWT”Keycloak nemá na rozdíl od většiny ostatních služeb vlastní host port ani subdoménu — je dostupný výhradně přes Traefik pravidlo
PathPrefix(/kc)na sdíleném web entrypointu (port 80, případněTRAEFIK_WEB_HOST_PORT, pokud je v.envpřenastavený). Nevymýšlejte si pro něj port:8180ani žádný jiný.
”invalid algorithm” v middleware
Section titled “”invalid algorithm” v middleware”Příčina: Strapi admin panel posílá HS256 JWT, ale middleware čeká RS256 (z Keycloaku).
Řešení: KC middleware dekóduje header a přeskočí non-RS256 tokeny:
const decoded = jwt.decode(token);
if (decoded?.header?.alg !== 'RS256') { return next(); // Přeskočit JWKS validaci}“Token expired”
Section titled ““Token expired””Příčina: KC JWT má TTL 12 hodin. Refresh token vypršel.
Řešení: Frontend volá endpoint /auth/refresh:
async function refreshTokenIfNeeded() { const decoded = jwtDecode(token.value); const expiresIn = decoded.exp * 1000 - Date.now();
if (expiresIn < 5 * 60 * 1000) { // Méně než 5 minut await $fetch('/api/auth/refresh', { method: 'POST' }); }}“audience mismatch” — 100% pádů přihlášení po přidání audience do jwt.verify()
Section titled ““audience mismatch” — 100% pádů přihlášení po přidání audience do jwt.verify()”Příčina: Předání audience: 'sencai-frontend' do options jwt.verify() knihovny jsonwebtoken vypadá jako zjevný způsob, jak middleware svázat s frontend klientem — ve skutečnosti to ale rozbilo úplně každé přihlášení. Vestavěná kontrola audience v jsonwebtoken porovnává výhradně proti claimu aud — a výchozí aud u tokenů sencai-frontend v Keycloaku je vestavěný klient account, ne sencai-frontend. Klient, který token skutečně vyžádal, je zaznamenaný zvlášť, v claimu azp.
Řešení: Nepoužívat vestavěnou volbu audience v jsonwebtoken. Nejdřív dekódovat, pak klienta ověřit ručně proti oběma claimům, azp i aud:
// sencai.space/src/middlewares/keycloak-jwt.ts (~řádky 206-226)// Vestavěná volba `audience` v jsonwebtoken kontroluje pouze claim `aud`,// ale skutečný požadující klient je v `azp`. Vynucení `audience: 'sencai-frontend'`// tady odmítlo úplně každý validní token (100% pádů přihlášení). Kontrola// azp/aud se místo toho dělá ručně až po jwt.verify().const azpMatches = decoded.azp === expectedClientId;const audMatches = Array.isArray(decoded.aud) ? decoded.aud.includes(expectedClientId) : decoded.aud === expectedClientId;
if (!azpMatches && !audMatches) { logger.error('❌ Keycloak JWT audience/azp mismatch:', { azp: decoded.azp, aud: decoded.aud }); reject(new Error(`JWT audience/azp mismatch: expected client "${expectedClientId}"`));}Tato oprava je v keycloak-jwt.ts pořád doprovázená inline komentářem vysvětlujícím proč — neodstraňujte ruční kontrolu azp/aud a znovu nepřidávejte audience do options jwt.verify().
User synchronization
Section titled “User synchronization””Loop in user-kc-sync”
Section titled “”Loop in user-kc-sync””Příčina: User update ve Strapi → webhook → auth-service-consumer → KC sync → Strapi update → nekonečná smyčka.
Řešení: Použít guard runWithoutKcSync() postavený na AsyncLocalStorage:
export function runWithoutKcSync<T>(fn: () => Promise<T>): Promise<T> { return asyncLocalStorage.run({ skipKcSync: true }, fn);}“Username is email instead of KC UUID”
Section titled ““Username is email instead of KC UUID””Příčina: Uživatel vytvořený před prvním KC přihlášením má username=email.
Řešení: KC JWT middleware automaticky aktualizuje username na KC sub:
if (decoded.preferred_username !== strapiUser.username) { await strapi.entityService.update( 'plugin::users-permissions.user', strapiUser.id, { data: { username: decoded.sub } } );}Keycloak setup
Section titled “Keycloak setup””KC returns 503”
Section titled “”KC returns 503””Příčina: Keycloak se pomalu startuje (normální jev).
Řešení: Počkat 30–90 sekund. Health check:
curl -f http://localhost/kc/health || exit 1“KC /kc endpoint 404”
Section titled ““KC /kc endpoint 404””Příčina: KC_HTTP_RELATIVE_PATH není nastaveno na /kc.
Řešení: V docker-compose.auth.yml:
environment: KC_HTTP_RELATIVE_PATH: "/kc" KEYCLOAK_URL: "http://keycloak:8080/kc"“Can’t get admin token”
Section titled ““Can’t get admin token””Příčina: Špatný grant type nebo realm.
Řešení: Použít password grant na realmu master s klientem admin-cli:
POST /realms/master/protocol/openid-connect/tokenclient_id=admin-cli&username=admin&password=<password>&grant_type=passwordUser management
Section titled “User management””User can’t verify email”
Section titled “”User can’t verify email””Příčina: Required action VERIFY_EMAIL není aktivní.
Řešení: Ve Strapi, když uživatel potvrdí email:
if (event.params.data.emailVerified) { await removeKcRequiredAction(user.id, 'VERIFY_EMAIL');}“Can’t complete profile (UPDATE_PROFILE loop)”
Section titled ““Can’t complete profile (UPDATE_PROFILE loop)””Příčina: Uživatel má required action UPDATE_PROFILE.
Řešení: Při vytváření KC uživatele nastavit firstName/lastName (fallback na '-'):
const kcUser = { username: registration.email, firstName: registration.name || '-', lastName: registration.surname || '-', requiredActions: ['VERIFY_EMAIL'], // Ne UPDATE_PROFILE};Realm configuration
Section titled “Realm configuration””Wrong realm”
Section titled “”Wrong realm””Příklady:
# ŠPATNĚ — používá master realmPOST http://keycloak:8080/realms/master/...
# SPRÁVNĚ — pro user token (sencai realm)POST http://keycloak:8080/realms/sencai/...
# SPRÁVNĚ — pro admin API (master realm)POST http://keycloak:8080/realms/master/...Client configuration
Section titled “Client configuration”| Client | Realm | Use case |
|---|---|---|
sencai-frontend | sencai | Přihlášení v prohlížeči (public client) |
auth-service-consumer | sencai | Backend volání KC admin API ze sync consumeru (confidential, client-secret) |
admin-cli | master | Admin API (password grant, master realm) |
V importu realmu neexistuje žádný client sencai-backend — neodkazovat na něj. Import realmu žije v orchestration/config/keycloak/data/import/sencai-realm.json.
”Client not found”
Section titled “”Client not found””Řešení: Ověřit v Keycloak Admin Console:
- Jít na
http://localhost/kc/admin - Vybrat realm
sencai - Zkontrolovat Clients → existují
sencai-frontendaauth-service-consumer
SMTP & email
Section titled “SMTP & email””Email not sending”
Section titled “”Email not sending””Řešení: Nakonfigurovat SMTP v Keycloaku:
- Realm
sencai - Realm settings → Email
- Vyplnit SMTP: host, port, username, password
- Kliknout Save
Frontend integration
Section titled “Frontend integration””Can’t exchange auth code”
Section titled “”Can’t exchange auth code””Příčina: Redirect URI není nakonfigurovaný v KC clientu.
Řešení: V Keycloak Adminu je skutečný client sencai-frontend (z orchestration/config/keycloak/data/import/sencai-realm.json) nakonfigurovaný takto:
-
Otevřít client
sencai-frontend -
Valid redirect URIs:
http://localhost/*(přes Traefik — toto je primární lokální URL)http://localhost:3000/*(přímý Nuxt dev server, mimo Traefik)https://app.sencai.space/*(prod)
-
Web Origins: nastaveno na wildcard
+, který důvěřuje jakémukoliv originu implikovanému nakonfigurovanými redirect URI — nejde o ručně udržovaný seznam jednotlivých originů.
”CORS error on KC token endpoint”
Section titled “”CORS error on KC token endpoint””Řešení: Nejdřív zkontrolovat, jestli Web Origins stále drží wildcard +. Pokud někdo nahradil + explicitním seznamem originů, je tento seznam pravděpodobnou příčinou — zkontrolovat, jestli v něm nechybí origin, než přidávat nové. Na ruční vypisování originů přejít jen tehdy, pokud existuje konkrétní důvod, proč nelze použít +.
Debugging
Section titled “Debugging”Zobrazení KC logů
Section titled “Zobrazení KC logů”docker logs -f sencai-keycloak-1 | grep -i errorOvěření JWT tokenu
Section titled “Ověření JWT tokenu”# Dekódovat token (bez verifikace)echo "token123" | jq -R 'split(".") | .[1] | @base64d | fromjson'KC health
Section titled “KC health”curl http://localhost/kc/health/ready# 200 = ready# 503 = startujeSync consumer issues
Section titled “Sync consumer issues”Toto jsou problémy specifické pro auth-service-consumer (viz auth-service-consumer/CLAUDE.md), odlišné od middleware problémů na straně Strapi popsaných výše.
KC 401 na admin token endpointu
Section titled “KC 401 na admin token endpointu”Příčina: KEYCLOAK_ADMIN_PASSWORD nastavené pro auth-service-consumer neodpovídá skutečnému admin heslu Keycloaku.
Řešení: Zajistit, aby KEYCLOAK_ADMIN_PASSWORD bylo identické mezi službou keycloak a auth-service-consumer v compose env (docker-compose.auth.yml / .env).
”User not found for update”
Section titled “”User not found for update””Příčina: Keycloak event dorazil do consumeru dřív, než odpovídající Strapi uživatel vůbec existoval (závod mezi registrací a zpracováním KC eventu).
Řešení: Consumer to zkouší znovu a navíc jako fallback hledá uživatele podle emailu, ne jen podle KC sub, než to vzdá.
Přetrvávající selhání po opakování
Section titled “Přetrvávající selhání po opakování”Příčina: Zpracování dál selhává i po MAX_RETRY_ATTEMPTS (výchozí 5, RETRY_DELAY_MS výchozí 12000 ms).
Řešení: Zpráva skončí ve frontě user.fallback v RabbitMQ (durable: true, TTL 14 dní). Zkontrolovat ji tam a projít logy consumeru kvůli konkrétní chybě.
KC → Strapi sync je jen pull-based
Section titled “KC → Strapi sync je jen pull-based”V současnosti neexistuje žádný instant push z Keycloaku do Strapi. Pokud je KC uživatel upravený přímo v Keycloak Admin Console, Strapi se to hned nedozví — sync probíhá pouze:
- Při přihlášení, přes middleware
keycloak-jwt(natáhne KC data uživatele a promítne je do Strapi), nebo - Na vyžádání, přes
POST /api/auth/sync-from-kc(autentizovaný endpoint, stejná logika volaná ručně z frontendu).
Keycloak Event Listener SPI plugin (Java) pro instant KC→Strapi push je naplánovaný, ale neimplementovaný — je vedený jako odložená práce v auth-service-consumer/CLAUDE.md. Nepředpokládat, že úpravy provedené přímo v Admin Console se do Strapi propíšou dřív než při dalším přihlášení nebo explicitním sync volání.
Checklist
Section titled “Checklist”- Běží KC? →
curl http://localhost/kc/health - JDBC konekce? → Zkontrolovat
docker logs sencai-keycloak-db-1 - Správný realm? → Ověřit v Admin Console
- Client existuje? → Zkontrolovat Clients (
sencai-frontend,auth-service-consumer) - JWT formát? → Dekódovat a ověřit
iss,sub,exp,azp - Email SMTP? → Zkontrolovat Realm settings → Email
- Sync loop? → Ověřit
runWithoutKcSync()v lifecycle - Sync consumer zdravý? → Zkontrolovat logy
auth-service-consumera frontuuser.fallback
Kompletní architekturu Keycloaku viz Root CLAUDE.md, implementaci syncu auth-service-consumer/.