Keycloak Authentication Architecture¶
Mozaiks ships with Keycloak as the default web authentication path.
This document explains the current auth model in the repo.
It is specifically about auth ownership and runtime flow. It does not define chat startup defaults or workflow entry selection.
Current Ownership¶
app/app.json¶
Owns the app-facing auth declaration and client-target manifest:
authRequiredadmins- target enablement under
targets.* - optional advanced
authoverrides - optional advanced
mobile.authoverrides
In the current manifest shape, this means:
authRequiredexpresses whether the app requires sign-inadminsexpresses the author-facing default admin listauth.*is advanced override territorymobile.auth.*is advanced native/mobile auth override territorytargets.*is the browser/mobile target declaration
app/app.json does not own:
chat.chat_startup_modeworkflows.entry_point
Those belong in app/config/ai.json.
app/config/ai.json¶
Owns app-level AI boot defaults such as:
chat.chat_startup_modeworkflows.entry_point
These settings affect how the app boots into chat/workflow mode. They do not define authentication.
Environment Variables¶
Own deployment-time backend overrides and local dev auth convenience, such as:
MOZAIKS_OIDC_AUTHORITYAUTH_AUDIENCEAUTH_REQUIRED_SCOPEAUTH_ROLES_CLAIMVITE_DEV_AUTH_MODEVITE_DEV_AUTOLOGINVITE_MOCK_MODE
app/brand/login-theme/¶
Owns Keycloak login-theme assets and templates.
This is separate from the in-app shell assets under app/brand/assets/.
Current Repo Note¶
The canonical target for generated/customer apps is a self-contained app workspace with app/app.json, app/config/ai.json, and app/brand/*. In this repo, the first-party Studio bundle follows that same contract through factory_app/app/app.json, factory_app/app/config/ai.json, and factory_app/app/brand/*.
Runtime Flow¶
app/app.json
-> host app boot config
-> web shell creates Keycloak auth adapter
-> user logs in through Keycloak
-> keycloak-js obtains tokens
-> frontend passes Bearer token to backend
-> backend validates JWT via OIDC discovery + JWKS
Separately:
app/config/ai.json
-> app-level chat startup mode
-> app-level default workflow selection
-> frontend boot selection only
Frontend¶
The web shell uses:
chat-ui/src/adapters/keycloakAuth.js
Key points:
- config is passed in from the host app
- no separate
auth.jsonis used - Keycloak uses Authorization Code + PKCE flow in the browser
- native/mobile auth selection is declared in
app/app.json -> mobile.auth
Backend¶
The backend auth layer lives under:
mozaiksai/core/auth/
Key pieces:
- config resolution
- OIDC discovery
- JWKS lookup
- JWT validation
- FastAPI route protection
- WebSocket authentication
The backend should be configured to validate against the same Keycloak realm and client that the frontend uses.
Current Configuration Rule¶
Use this order of operations:
- declare app-facing auth in
app/app.json - declare app-level chat/workflow boot defaults in
app/config/ai.json - use environment variables for deployment overrides
- keep Keycloak login-theme assets under
app/brand/login-theme/
Do not reintroduce:
auth.json- split frontend/backend auth files for the same app
Do not move entry_point or chat.chat_startup_mode into app/app.json.
Minimal Example¶
Separate app-level boot example:
That example belongs in app/config/ai.json, not in app/app.json.
For mobile:
{
"mobile": {
"auth": {
"provider": "token",
"redirectScheme": "myapp",
"redirectPath": "oauthredirect",
"scopes": ["openid", "profile", "email"]
}
}
}
For web, keep the target declaration minimal:
For desktop, do not over-specify config until a real desktop client exists. Today this is usually enough:
That field currently expresses intent, not a full desktop runtime contract.
For the broader manifest model, read App Manifest And Platform Targets.
Verification¶
After auth changes:
- verify
app/app.jsonparses - verify
app/config/ai.jsonstill contains only app-level AI boot settings - verify the web shell can initialize the Keycloak adapter
- verify login redirects to the intended Keycloak realm
- verify backend-protected routes accept the resulting token