Tento dokument popisuje, jak přesně aplikace (postavená na fancyadmin) komunikuje s Keycloak serverem — jaké OAuth2/OIDC flows používá, jaké endpointy volá a jaké endpointy sama vystavuje. Určeno pro security review a pro provozovatele vlastního Keycloak serveru.
Návod na integraci na straně projektu je v README.md, sekce 18. Keycloak SSO integrace.
- Přehled použitých flows
- Endpointy Keycloaku, které aplikace volá
- Endpointy, které aplikace vystavuje
- Flow: Přihlášení
- Flow: Odhlášení
- Flow: Backchannel logout
- Flow: Změna hesla
- Frontend — správa session v prohlížeči
- Admin API — správa uživatelů
- Bezpečnostní mechanismy
- Dvoufázové ověření WebAuthn klíčem
| Flow | Účel | Klient |
|---|---|---|
| Authorization Code Flow s PKCE (S256) | Přihlášení uživatele | confidential |
Authorization Code Flow s PKCE a prompt=none |
Silent SSO check (ověření existující KC session bez interakce) | confidential |
| Client Credentials Grant | Získání admin tokenu pro Admin API | confidential (service account) |
| OIDC Backchannel Logout | Invalidace aplikační session při odhlášení v KC | confidential |
Application-Initiated Action (kc_action=UPDATE_PASSWORD) |
Změna hesla uživatele v KC z aplikace | confidential |
| check-sso + silent iframe (keycloak-js, PKCE S256) | Frontend monitoring KC session, token refresh | public |
Aplikace nepoužívá: Implicit Flow, Direct Access Grants (ROPC), offline tokeny. Implementace odpovídá požadavkům OAuth 2.1 (PKCE u všech autorizačních requestů, state jako CSRF ochrana, exact redirect URI matching, žádný ROPC).
| Endpoint | Metoda | Účel |
|---|---|---|
/realms/{realm}/protocol/openid-connect/token |
POST | Výměna authorization code za tokeny (grant_type=authorization_code); získání admin tokenu (grant_type=client_credentials) |
/realms/{realm}/protocol/openid-connect/userinfo |
GET | Získání user claims (email, jméno) z access tokenu |
/realms/{realm}/protocol/openid-connect/certs |
GET | JWKS — podpisové klíče realmu pro validaci backchannel logout tokenu (cachováno 1 h) |
/admin/realms/{realm}/users |
GET | Vyhledání uživatele podle emailu/username (výsledek cachován 1 h) |
/admin/realms/{realm}/users |
POST | Vytvoření uživatele |
/admin/realms/{realm}/users/{id} |
GET | Získání uživatele podle KC ID (při backchannel logoutu) |
/admin/realms/{realm}/users/{id} |
PUT | Aktualizace údajů / enable / disable |
/admin/realms/{realm}/users/{id}/reset-password |
PUT | Nastavení hesla |
/admin/realms/{realm}/users/{id}/execute-actions-email |
PUT | Odeslání emailu pro reset hesla (UPDATE_PASSWORD) |
Všechna Admin API volání jsou autentizována Bearer tokenem získaným přes client_credentials grant. Timeout HTTP klienta je 5 s.
| Endpoint | Účel |
|---|---|
/realms/{realm}/protocol/openid-connect/auth |
Autorizační endpoint — přihlášení, silent check (prompt=none), změna hesla (kc_action=UPDATE_PASSWORD) |
/realms/{realm}/protocol/openid-connect/logout |
RP-initiated logout (s id_token_hint, post_logout_redirect_uri, client_id, state) |
Autorizační request vždy obsahuje: client_id, response_type=code, redirect_uri, scope=openid email profile, state (náhodný CSRF token vázaný na session), code_challenge + code_challenge_method=S256 (PKCE), volitelně login_hint (předvyplnění emailu), ui_locales, kc_action.
Návratová URL se do Keycloaku neposílá — drží se v serverové session pod klíčem state a callback si ji vyzvedne po ověření state. redirect_uri jsou proto statická a v konfiguraci KC klienta se vyjmenovávají jako exact matches (bez wildcardů).
Všechny jsou v routě keycloak-auth/* resp. keycloak-log/*:
| Endpoint | Metoda | Volá | Účel |
|---|---|---|---|
/keycloak-auth/callback?instance={name}&code=...&state=... |
GET | prohlížeč (redirect z KC) | OAuth2 callback — ověření state proti session, výměna code za tokeny (s PKCE verifierem), přihlášení uživatele |
/keycloak-auth/silent-check?instance={name}&code=...&state=... |
GET | prohlížeč (redirect z KC) | Callback pro silent SSO check (prompt=none), stejná validace state/PKCE |
/keycloak-auth/post-log-out?state=... |
GET | prohlížeč (redirect z KC) | Návrat po logoutu z KC, redirect na state |
/keycloak-auth/backchannel-logout?instance={name} |
POST | Keycloak server | OIDC backchannel logout — přijímá logout_token (JWT) |
/keycloak-auth/silent-check-sso |
GET | prohlížeč (iframe keycloak-js) | Stránka pro silent check iframe adapteru |
/keycloak-log/out |
GET | prohlížeč | Mezistránka logoutu (spinner + JS redirect na KC logout) |
Důležité pro síťovou konfiguraci: endpoint
backchannel-logoutvolá Keycloak server přímo (server-to-server POST). Musí být dostupný z Keycloak serveru. Ostatní endpointy volá jen prohlížeč uživatele.
uživatel aplikace Keycloak
│ │ │
│ 1. zadá email │ │
│───────────────────────►│ │
│ │ 2. lookup: má identita │
│ │ (nebo její role) SSO? │
│ │ │
│ 3. redirect na /auth (login_hint=email, state=CSRF token,
│ code_challenge=S256; návratová URL zůstává v session)
│────────────────────────────────────────────────────────►
│ │ │
│ 4. přihlášení v KC │ │
│◄───────────────────────────────────────────────────────│
│ │ │
│ 5. redirect na /keycloak-auth/callback?code=...&state=...&instance=...
│───────────────────────►│ │
│ │ 5a. ověření state proti session (CSRF)
│ │ │
│ │ 6. POST /token │
│ │ (authorization_code + client_secret + code_verifier)
│ │──────────────────────────────►│
│ │◄──────────────────────────────│
│ │ access_token, id_token │
│ │ │
│ │ 7. GET /userinfo │
│ │──────────────────────────────►│
│ │◄──────────────────────────────│
│ │ email, given_name, ... │
│ │ │
│ │ 8. párování na lokální │
│ │ identitu podle emailu, │
│ │ vytvoření app session │
│ 9. redirect na návratovou URL ze session │
│◄───────────────────────│ │
Klíčové body:
stateje náhodný jednorázový CSRF token — při startu flow se uloží do serverové session (spolu s PKCE code_verifierem a návratovou URL), callback ho ověří a zneplatní; neznámý/expirovaný state (TTL 10 min) flow ukončí. Podvržený callback s cizím authorization code tak nelze do session oběti injektovat.- PKCE (S256) — code_verifier drží serverová session, k token requestu se přikládá při výměně code za tokeny
- Párování uživatele probíhá podle emailu — email z KC userinfo se hledá v lokální tabulce
identity - Pokud lokální identita neexistuje a je zapnutá auto-registrace, vytvoří se s výchozí rolí z konfigurace SSO instance
- Pokud
/userinfoselže, claims se čtou fallbackem zid_token(bez validace podpisu — jde o data z přímé TLS-ověřené komunikace s KC, ne od uživatele) id_tokense ukládá do serverové session (pro pozdější logout sid_token_hint), access/refresh token backend neukládá — aplikační session je od té chvíle nezávislá, s výjimkou backchannel logoutu a frontend monitoringu (viz níže)
- Uživatel klikne na odhlásit → aplikace ukončí lokální session
- Pokud se uživatel přihlásil přes SSO (v session je
id_token), aplikace sestaví KC logout URL:{hostUrl}/realms/{realm}/protocol/openid-connect/logout ?post_logout_redirect_uri={app}/keycloak-auth/post-log-out &id_token_hint={id_token} &client_id={clientId} &state={návratová URL} - Prohlížeč je přesměrován přes mezistránku
/keycloak-log/outna KC logout endpoint - KC ukončí SSO session a přesměruje zpět na
/keycloak-auth/post-log-out, který uživatele vrátí na login stránku
Při ukončení KC session z jakéhokoli důvodu (odhlášení z jiné aplikace, expirace SSO session, deaktivace uživatele adminem):
- Keycloak pošle
POST /keycloak-auth/backchannel-logout?instance={name}s tělemlogout_token={JWT} - Aplikace token plně zvaliduje podle OIDC Back-Channel Logout 1.0:
- podpis proti JWKS realmu (
/certs, cachováno 1 h, při rotaci klíčů se cache jednou obnoví) iss(issuer realmu),aud(client_id aplikace),exp/iateventsmusí obsahovathttp://schemas.openid.net/event/backchannel-logoutnoncenesmí být přítomen,sub/sidmusíjtise přijme jen jednou (replay ochrana)
- podpis proti JWKS realmu (
- Z validovaného tokenu přečte claim
sub(KC user ID) - Přes Admin API (
GET /admin/realms/{realm}/users/{sub}) zjistí email uživatele - Najde lokální identitu podle emailu a invaliduje všechny její aplikační sessions
- Odpoví
200 OK(chybové stavy:400při chybějícím/nevalidním tokenu nebo neznámé instanci)
Aplikace nikdy nezpracovává staré ani nové heslo — vše probíhá v KC:
- Aplikace přesměruje uživatele na autorizační endpoint s
kc_action=UPDATE_PASSWORD - KC si vyžádá re-autentizaci současným heslem, ohlídá password policy a 2FA
- Po změně KC přesměruje zpět na
/keycloak-auth/callbackskc_action_status=success - Aplikace propíše success stav do návratové URL (parametr
kcActionSuccess=1)
Alternativně reset hesla emailem: aplikace zavolá Admin API execute-actions-email s akcí UPDATE_PASSWORD — email s odkazem posílá Keycloak (vyžaduje nakonfigurované SMTP v realmu).
Po přihlášení přes SSO běží v prohlížeči adapter keycloak-js (oficiální KC adapter) proti public klientovi:
- Inicializace s
onLoad: 'check-sso'+ silent check iframe (/keycloak-auth/silent-check-sso) - Session status iframe (
checkLoginIframe) — kontrola stavu KC session každých 30 s; při detekci odhlášení (onAuthLogout) okamžitý redirect na logout aplikace - Token refresh — každých 30 s, pokud tokenu zbývá < 60 s platnosti; při selhání refresh (KC session skončila) odhlášení z aplikace
- Page Visibility API — ve skryté záložce se refresh pozastavuje, aby neaktivní záložka neudržovala KC session naživu (SSO Session Idle timeout reálně tiká); při návratu do záložky se token hned obnoví, případně se uživatel odhlásí
Frontend tak zajišťuje, že platnost aplikační session je fakticky svázána s platností KC SSO session.
Prohlížeče blokující 3rd-party cookies (Safari, iOS včetně PWA na ploše): oba iframy (silent check i session status) tam z principu nefungují a adapter je sám vypne. Ve výchozím nastavení by se check-sso degradoval na plný redirect celého okna na Keycloak s redirect_uri = aktuální URL stránky — a protože klient používá exact redirect URI matching, Keycloak takový request odmítne chybovou stránkou Invalid parameter: redirect_uri. Adapter se proto inicializuje s silentCheckSsoFallback: false, aby se check-sso v takovém prohlížeči jen tiše vzdal. Frontend monitoring session tam tedy neběží; ukončení KC session se do aplikace propíše přes backchannel logout (viz kapitola 6).
Aplikace používá KC Admin API pro synchronizaci uživatelů (volitelné, dle využití v konkrétním projektu):
- vyhledání uživatele podle emailu (s 1h cache na straně aplikace)
- vytvoření uživatele (s heslem, s dočasným heslem, nebo s vynucením nastavení hesla při prvním přihlášení)
- aktualizace údajů (email, jméno, příjmení)
- enable/disable uživatele
- nastavení hesla
- odeslání reset-password emailu
Vyžadovaná oprávnění service accountu: pouze manage-users z klienta realm-management. Aplikace nepotřebuje žádná realm-admin ani jiná oprávnění.
- PKCE (S256) na všech autorizačních requestech — backend confidential client i frontend keycloak-js public client; chrání proti code interception a code injection (vyžadováno OAuth 2.1)
statejako CSRF ochrana — náhodný jednorázový token vázaný na serverovou session (TTL 10 min, one-time use); návratová URL se nepřenáší přes Keycloak, drží se v session- Confidential client — výměna code za token probíhá výhradně server-to-server s client_secret; tokeny nikdy neprochází prohlížečem (kromě tokenů public klienta pro keycloak-js, což je standardní model KC)
- TLS validace — backend komunikace s KC má zapnutou validaci TLS certifikátu (vypnutí je možné jen explicitní config volbou pro lokální vývoj)
- redirect_uri validace —
redirect_urijsou statická (bez proměnlivých parametrů) a v konfiguraci KC klienta whitelistovaná jako exact matches bez wildcardů; při výměně code za token se posílá totožnéredirect_urijako v autorizačním requestu (KC ho validuje) - Loop detection — callback endpoint má ochranu proti redirect smyčce (max 3 pokusy o autentizaci za 120 s, poté redirect na login stránku)
- Ochrana proti open redirectu — návratové URL pochází výhradně ze serverové session (generované aplikací); post-logout
statese navíc validuje na shodu hostu s aplikací - Backchannel logout —
logout_tokense plně validuje podle OIDC spec (podpis proti JWKS, iss, aud, events, replay ochrana přes jti); identita uživatele se navíc ověřuje zpětným dotazem na KC Admin API (sub→ uživatel → email) - Žádné ukládání tokenů v DB —
id_tokenje pouze v serverové session (pro logout), access/refresh tokeny backend nedrží - Druhý faktor plně v KC — WebAuthn ceremonie i credentials jsou na straně Keycloaku; aplikace klíče nevidí, neukládá a nijak s nimi nepracuje, jejich správa probíhá výhradně v Keycloaku (viz sekce 11)
Volitelný druhý faktor pro SSO uživatele, řešený čistě konfigurací Keycloak flow. Kdo si klíč nezaregistruje, přihlašuje se dál jen heslem — o volitelnost se stará conditional subflow v KC, ne aplikace. Aplikace do 2FA nijak nezasahuje; registraci i odebírání klíčů řeší administrátor v administraci Keycloaku.
| Kde | Nastavení |
|---|---|
| Authentication → Policies → WebAuthn Policy | Relying Party ID = doména KC serveru (bez schématu a portu), Require Resident Key = No, User Verification = preferred, Signature Algorithms = ES256 (+RS256) |
| Authentication → Flows | kopie browser flow, do subflow browser forms za Username Password Form přidat conditional subflow s Condition - user configured + WebAuthn Authenticator (Required) |
| Clients → confidential client → Advanced | Authentication flow overrides → Browser Flow = nový flow (omezí 2FA jen na tuto aplikaci) |
| Authentication → Required Actions | Webauthn Register: Enabled = On, Set as default action = Off (jinak si klíč musí zaregistrovat každý nový uživatel a 2FA přestane být volitelná) |
Provozní poznámky:
Relying Party IDnelze později změnit bez zneplatnění všech registrovaných klíčů. KC musí běžet na HTTPS (nebolocalhost) — WebAuthn v nezabezpečeném kontextu nefunguje.- Ztráta klíče = zablokovaný účet. Doporučuje se povolit
Recovery Authentication Codes, nebo dátOTP Formdo conditional subflow jako Alternative. Bez záložního faktoru musí klíč odebrat administrátor v administraci Keycloaku. - Silent SSO (
prompt=none) i backchannel logout fungují bez změny — druhý faktor se řeší jen při vytváření KC session. - CSP aplikace se nemění: WebAuthn ceremonie běží na doméně KC, ne na doméně aplikace.
Registraci klíče spustí administrátor tak, že uživateli v administraci Keycloaku (detail uživatele → Required user actions) přiřadí akci Webauthn Register, uživatel si pak klíč zaregistruje při příštím přihlášení. Odebrání klíče provede administrátor tamtéž (detail uživatele → Credentials). Chce-li uživatel 2FA zapnout, vypnout nebo vyměnit klíč, obrací se na administrátora.
Aplikace v tom nehraje žádnou roli: projekt nepotřebuje žádnou migraci, entitu ani další glue třídy a v databázi aplikace se o klíčích neukládá nic.