Přeskočit na obsah

Keycloak autentizace

Keycloak je OIDC identity provider platformy Sencai. Řeší registraci uživatelů, přihlašování, JWT tokeny, TOTP/2FA a SSO integrace.

  • Verze: Latest
  • Realm: sencai
  • Port: 8080 (pouze interní port kontejneru, bez vlastního externího host portu — dostupný výhradně přes Traefik PathPrefix('/kc') na sdíleném web entrypointu)
  • Databáze: MySQL (keycloak-db:3307)
  • URL (interní): http://keycloak:8080/kc
  • Admin (lokálně, přes Traefik): http://app.sencai.localhost/kc/admin (přihlašovací údaje: admin/admin)

Keycloak má nakonfigurováno více clientů:

Client IDTypÚčelGrant Type
sencai-frontendPublicNuxt/Vue frontendauthorization_code
sencai-backendConfidentialStrapi backendclient_credentials
sencai-adminConfidentialAdmin UIauthorization_code
admin-cliConfidentialAdmin API (master realm)password
  • Access Token: 12 hodin (KC_ACCESS_TOKEN_LIFESPAN=12h) na úrovni realmu. Samotný Nuxt frontend si ale vydává vlastní, kratší cookies access_token/id_token (15 min) a 30denní cookie refresh_token (httpOnly) — viz níže.
  • Refresh Token: 30 dní (httpOnly cookie)

Platformní frontend (sencai.space-frontend) obnovuje access token proaktivně, místo aby čekal na jeho expiraci:

  • Nuxt plugin auth-refresh-scheduler.client.ts dekóduje claim exp aktuálního access tokenu a naplánuje refresh 60 sekund před expirací. Plán se přeplánuje při každé změně tokenu (login, refresh, logout) a pozastaví se, dokud je karta prohlížeče skrytá.
  • Samotný refresh je v auth store řešen jako single-flight akce: souběžní volající sdílí jeden probíhající refresh, dočasné chyby se opakují a odhlášení (redirect na /auth/login s sessionExpired=1) vyvolá pouze skutečná 401 z refresh endpointu.
  • Na straně serveru POST /api/auth/refresh (Nuxt server route) přečte httpOnly cookie refresh_token, vymění ji u Keycloak token endpointu (grant_type=refresh_token) a z odpovědi znovu nastaví cookies access_token, refresh_token a id_token.
{
"sub": "550e8400-e29b-41d4-a716-446655440000",
"email": "user@example.com",
"name": "John Doe",
"family_name": "Doe",
"given_name": "John",
"email_verified": true,
"preferred_username": "user@example.com",
"iat": 1234567890,
"exp": 1234571490,
"iss": "http://keycloak:8080/kc/realms/sencai",
"aud": "sencai-frontend",
"typ": "Bearer"
}

Strapi validuje token přes JWKS (JSON Web Key Set) endpoint:

http://keycloak:8080/kc/realms/sencai/.well-known/openid-configuration
http://keycloak:8080/kc/realms/sencai/protocol/openid-connect/certs
  1. Uživatel se zaregistruje přes formulář na frontendu
  2. Strapi POST /api/registrations
  3. Strapi webhook spustí: POST webhook-publisher:1347
  4. Event: { eventName: 'registration.create', entity: registration, ... }
  5. RabbitMQ → auth-service-consumer
  6. auth-service-consumer zavolá KC admin API:
    POST /admin/realms/sencai/users
    {
    "username": "550e8400-...", // UUID z KC sub
    "email": "user@example.com",
    "firstName": "John",
    "lastName": "Doe",
    "enabled": true,
    "requiredActions": ["VERIFY_EMAIL"]
    }
  7. KC automaticky odešle verifikační email
  8. Uživatel klikne na odkaz → email je ověřen
  9. Admin ve Strapi UI nastaví Registration.emailVerified = true
  10. Strapi lifecycle hook se spustí → webhook-publisher
  11. auth-service-consumer odstraní required action VERIFY_EMAIL
  12. Uživatel se může přihlásit

Přihlášení (dokončení profilu při prvním loginu)

Section titled “Přihlášení (dokončení profilu při prvním loginu)”
  1. Uživatel se přihlásí přes KC login formulář
  2. KC vydá JWT
  3. Frontend přesměruje na /auth/callback s code + state
  4. Frontend vymění code za JWT
  5. Strapi middleware keycloak-jwt ověří JWT
  6. Middleware zkontroluje, že existuje Strapi User se shodným emailem
  7. Pokud chybí name nebo surname: přesměrování na /auth/complete-profile
  8. Uživatel vyplní profil → POST /api/auth/complete-profile
  9. Strapi aktualizuje Usera → lifecycle hook → webhook-publisher → KC sync
  10. Atributy Strapi Usera a KC uživatele jsou sladěny
  11. Přesměrování na /gravity/dashboard

Keycloak umožňuje federovat externí identity providery:

  1. KC Admin → Realm Settings → Identity Providers → Create → Google
  2. Zadat Google OAuth přihlašovací údaje (client_id, client_secret)
  3. Uložit
  4. Přihlašovací stránka zobrazí tlačítko Google
  5. Uživatel klikne → přesměrování na Google login
  6. Google přesměruje zpět s access tokenem
  7. KC vytvoří/napojí uživatele
  1. KC Admin → Identity Providers → Create → SAML v2.0
  2. Nahrát SAML metadata firmy nebo nakonfigurovat ručně
  3. Nakonfigurovat mapování assertion (email → KC email atd.)
  4. SAML IdP firmy přesměruje na KC assertion consumer URL
  5. KC napojí uživatele na účet Sencai

Keycloak umí federovat libovolný OIDC provider (Okta, Azure AD, vlastní):

  1. KC Admin → Identity Providers → Create → OpenID Connect v1.0
  2. Zadat Authorization URL, Token URL, Logout URL
  3. Přidat přihlašovací údaje clienta
  4. Namapovat claimy na atributy KC uživatele
  5. Uložit
  1. Uživatel jde do Account Settings → Security → 2FA
  2. Klikne na Enable 2FA
  3. Zobrazí se QR kód (obsahuje secret + issuer)
  4. Uživatel jej naskenuje v Google Authenticator / Authy / 1Password
  5. Uživatel zadá 6místný kód z aplikace
  6. KC ověří kód a zapne TOTP
  7. Zobrazí se recovery kódy (uložit pro obnovu účtu)

TOTP využívá knihovnu Speakeasy:

Required Action: Configure OTP
Algorithm: TOTP (time-based)
Digit: 6
Period: 30s

KC může uživatele donutit dokončit určité akce:

ActionSpuštěnoDetaily
VERIFY_EMAILPři registraciUživatel musí kliknout na verifikační odkaz v emailu
UPDATE_PROFILENastaví adminUživatel musí při dalším přihlášení vyplnit jméno/příjmení
CONFIGURE_OTPNastaví adminUživatel si musí nastavit 2FA
terms_and_conditionsCustomUživatel musí odsouhlasit obchodní podmínky

Když se uživatel s nedokončenými required actions přihlásí, KC jej před vydáním access tokenu přesměruje do action flow.

Pro potřeby auth-service-consumer při správě KC uživatelů:

Terminál
POST http://keycloak:8080/kc/realms/master/protocol/openid-connect/token
Content-Type: application/x-www-form-urlencoded
grant_type=password
&client_id=admin-cli
&client_secret=<SECRET>
&username=admin
&password=admin

Odpověď:

{
"access_token": "eyJ...",
"expires_in": 3600,
"token_type": "Bearer"
}

Poté použít token pro volání admin API:

Terminál
POST http://keycloak:8080/kc/admin/realms/sencai/users
Authorization: Bearer eyJ...
{ "username": "user", "email": "user@example.com", ... }

Sencai upravuje KC přihlašovací, registrační a emailové šablony:

Soubory: auth-service/themes/

  • login/ — HTML/CSS přihlašovacího formuláře
  • register/ — Registrační formulář
  • email/ — Emailové šablony (verifikace, reset hesla)

Nahrát přes KC Admin → Realm Settings → Themes.

Strapi User.afterCreate/Update
→ webhook-publisher (event: user.update)
→ RabbitMQ user.events
→ auth-service-consumer
→ KC admin API: PUT /admin/realms/sencai/users/{id}
→ atributy v KC aktualizovány

Při přihlášení Strapi ověří, jestli je profil uživatele kompletní. Pokud ne:

keycloak-jwt middleware
→ fetchKcUserById(decoded.sub)
→ runWithoutKcSync(syncKcUserToStrapi)
→ Strapi User aktualizován z KC
→ žádná zpětná smyčka (runWithoutKcSync brání aktualizaci KC)

Q: KC hlásí 503 při startu

A: Keycloak potřebuje 30–90s na start. Počkejte a zkuste znovu. Zkontrolujte logy: docker logs keycloak

Q: Chyba JWT „invalid algorithm”

A: Strapi admin panel používá HS256 tokeny. Middleware musí dekódovat hlavičku, zkontrolovat alg a přeskočit tokeny, které nejsou RS256.

Q: „Account is not fully set up”

A: Uživatel má required action UPDATE_PROFILE. Zkontrolujte, že jsou v atributech KC uživatele vyplněny firstName/lastName.

Q: KC URL vrací 404

A: Ujistěte se, že je přítomen prefix /kc: KEYCLOAK_URL=http://keycloak:8080/kc (včetně koncového /kc).

Q: Token endpoint vrací 404

A: Token endpoint je na /kc/realms/sencai/protocol/openid-connect/token. Ověřte celou URL včetně prefixu /kc.