Keycloak authentication
Keycloak Authentication
Section titled “Keycloak Authentication”Keycloak is the OIDC identity provider for Sencai. It manages user registration, login, JWT tokens, TOTP/2FA, and SSO integrations.
Key Facts
Section titled “Key Facts”- Version: Latest
- Realm:
sencai - Port: 8080 (internal container port only, no dedicated external host port — reached exclusively through Traefik
PathPrefix('/kc')on the shared web entrypoint) - Database: MySQL (keycloak-db:3307)
- URL (internal):
http://keycloak:8080/kc - Admin (local, via Traefik):
http://app.sencai.localhost/kc/admin(creds: admin/admin)
Clients
Section titled “Clients”Keycloak has multiple clients configured:
| Client ID | Type | Purpose | Grant Type |
|---|---|---|---|
sencai-frontend | Public | Nuxt/Vue frontend | authorization_code |
sencai-backend | Confidential | Strapi backend | client_credentials |
sencai-admin | Confidential | Admin UI | authorization_code |
admin-cli | Confidential | Admin API (master realm) | password |
JWT Tokens
Section titled “JWT Tokens”Token Lifespan
Section titled “Token Lifespan”- Access Token: 12 hours (
KC_ACCESS_TOKEN_LIFESPAN=12h) at the realm level. The Nuxt frontend itself issues shorter-livedaccess_token/id_tokencookies (15 min) and a 30-dayrefresh_tokencookie (httpOnly) — see below. - Refresh Token: 30 days (httpOnly cookie)
Frontend Refresh Flow
Section titled “Frontend Refresh Flow”The platform frontend (sencai.space-frontend) proactively refreshes the access token instead of waiting for it to expire:
- A Nuxt plugin,
auth-refresh-scheduler.client.ts, decodes the current access token’sexpclaim and schedules a refresh 60 seconds before expiry. It re-arms on every token change (login, refresh, logout) and pauses while the browser tab is hidden. - The refresh itself is a single-flight action in the auth store: concurrent callers share one in-flight refresh, transient failures are retried, and only a genuine
401from the refresh endpoint triggers logout (redirect to/auth/loginwithsessionExpired=1). - Server-side,
POST /api/auth/refresh(a Nuxt server route) reads therefresh_tokenhttpOnly cookie, exchanges it with Keycloak’s token endpoint (grant_type=refresh_token), and re-sets theaccess_token,refresh_token, andid_tokencookies from the response.
Access Token Content (RS256)
Section titled “Access Token Content (RS256)”{ "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"}Validation
Section titled “Validation”Strapi validates via JWKS (JSON Web Key Set) endpoint:
http://keycloak:8080/kc/realms/sencai/.well-known/openid-configurationhttp://keycloak:8080/kc/realms/sencai/protocol/openid-connect/certsUser Provisioning Flow
Section titled “User Provisioning Flow”Registration (Strapi → KC)
Section titled “Registration (Strapi → KC)”- User registers via frontend form
- Strapi POST
/api/registrations - Strapi webhook triggers:
POST webhook-publisher:1347 - Event:
{ eventName: 'registration.create', entity: registration, ... } - RabbitMQ → auth-service-consumer
- auth-service-consumer calls KC admin API:
POST /admin/realms/sencai/users{"username": "550e8400-...", // UUID from KC sub"email": "user@example.com","firstName": "John","lastName": "Doe","enabled": true,"requiredActions": ["VERIFY_EMAIL"]}
- KC sends verification email automatically
- User clicks link → email verified
- Admin in Strapi UI sets
Registration.emailVerified = true - Strapi lifecycle hook fires → webhook-publisher
- auth-service-consumer removes
VERIFY_EMAILrequired action - User can log in
Login (First-Time Profile Completion)
Section titled “Login (First-Time Profile Completion)”- User logs in via KC login form
- KC issues JWT
- Frontend redirects to
/auth/callbackwith code + state - Frontend exchanges code for JWT
- Strapi keycloak-jwt middleware validates JWT
- Middleware checks Strapi User exists with matching email
- If missing
nameorsurname: redirect to/auth/complete-profile - User fills profile → POST
/api/auth/complete-profile - Strapi updates User → lifecycle hook → webhook-publisher → KC sync
- Strapi User and KC user attributes aligned
- Redirect to
/gravity/dashboard
SSO Integration
Section titled “SSO Integration”Keycloak supports federating external identity providers:
Google OIDC
Section titled “Google OIDC”- KC Admin → Realm Settings → Identity Providers → Create → Google
- Add Google OAuth credentials (client_id, client_secret)
- Save
- Login page shows Google button
- User clicks → redirects to Google login
- Google redirects back with access token
- KC creates/links user
SAML 2.0 (Corporate)
Section titled “SAML 2.0 (Corporate)”- KC Admin → Identity Providers → Create → SAML v2.0
- Upload company’s SAML metadata or configure manually
- Configure assertion mapping (email → KC email, etc.)
- Company’s SAML IdP redirects to KC assertion consumer URL
- KC links user to Sencai account
Custom OIDC
Section titled “Custom OIDC”Keycloak can federate any OIDC provider (Okta, Azure AD, custom):
- KC Admin → Identity Providers → Create → OpenID Connect v1.0
- Enter Authorization URL, Token URL, Logout URL
- Add client credentials
- Map claims to KC user attributes
- Save
TOTP/2FA Setup
Section titled “TOTP/2FA Setup”Enabling 2FA for User
Section titled “Enabling 2FA for User”- User goes to Account Settings → Security → 2FA
- Click Enable 2FA
- QR code appears (shows secret + issuer)
- User scans in Google Authenticator / Authy / 1Password
- User enters 6-digit code from app
- KC verifies code and enables TOTP
- Recovery codes displayed (save for account recovery)
Keycloak Configuration
Section titled “Keycloak Configuration”TOTP uses Speakeasy library:
Required Action: Configure OTPAlgorithm: TOTP (time-based)Digit: 6Period: 30sRequired Actions
Section titled “Required Actions”KC can force users to complete actions:
| Action | Trigger | Details |
|---|---|---|
VERIFY_EMAIL | On registration | User must click email verification link |
UPDATE_PROFILE | Admin sets | User must fill name/surname on next login |
CONFIGURE_OTP | Admin sets | User must set up 2FA |
terms_and_conditions | Custom | User must accept T&C |
When user logs in with required actions pending, KC redirects to action flow before granting access token.
Admin API Access
Section titled “Admin API Access”Grant Type: password
Section titled “Grant Type: password”For auth-service-consumer to manage KC users:
POST http://keycloak:8080/kc/realms/master/protocol/openid-connect/tokenContent-Type: application/x-www-form-urlencoded
grant_type=password&client_id=admin-cli&client_secret=<SECRET>&username=admin&password=adminResponse:
{ "access_token": "eyJ...", "expires_in": 3600, "token_type": "Bearer"}Then use token to call admin API:
POST http://keycloak:8080/kc/admin/realms/sencai/usersAuthorization: Bearer eyJ...
{ "username": "user", "email": "user@example.com", ... }Themes
Section titled “Themes”Sencai customizes KC login, registration, and email templates:
Files: auth-service/themes/
login/— Login form HTML/CSSregister/— Registration formemail/— Email templates (verification, password reset)
Upload via KC Admin → Realm Settings → Themes.
Keycloak ↔ Strapi Sync
Section titled “Keycloak ↔ Strapi Sync”Strapi → KC (Write)
Section titled “Strapi → KC (Write)”Strapi User.afterCreate/Update → webhook-publisher (event: user.update) → RabbitMQ user.events → auth-service-consumer → KC admin API: PUT /admin/realms/sencai/users/{id} → KC attributes updatedKC → Strapi (Read)
Section titled “KC → Strapi (Read)”On login, Strapi checks if user profile is complete. If not:
keycloak-jwt middleware → fetchKcUserById(decoded.sub) → runWithoutKcSync(syncKcUserToStrapi) → Strapi User updated from KC → No feedback loop (runWithoutKcSync prevents KC update)Troubleshooting
Section titled “Troubleshooting”Q: KC 503 on startup
A: Keycloak takes 30–90s to start. Wait and retry. Check logs: docker logs keycloak
Q: “invalid algorithm” JWT error
A: Strapi admin panel uses HS256 tokens. Middleware must decode header to check alg and skip non-RS256 tokens.
Q: “Account is not fully set up”
A: User has UPDATE_PROFILE required action. Ensure firstName/lastName are set in KC user attributes.
Q: KC URL redirects with 404
A: Ensure /kc prefix is present: KEYCLOAK_URL=http://keycloak:8080/kc (with trailing /kc).
Q: Token endpoint 404
A: Token endpoint is at /kc/realms/sencai/protocol/openid-connect/token. Verify full URL including /kc prefix.