Identity Provider Design
ContainerPub's backends sit between clients and the identity provider (today Keycloak). Dashboards and the CLI never talk to Keycloak directly. This page explains why, what the backend does, and the plan to make the provider replaceable by changing one client in the backend.
The first sections describe how it works today. "Planned: IdentityProvider interface" is a design, not yet implemented.
Why the backend mediates auth
- Local user record. Functions, API keys and sites hang off the Postgres users table, which must stay in sync with the identity provider. The backend creates both together (UserSyncService.createUser).
- Invite-only signup. Registration is gated by early-access capacity and feature flags. A direct provider signup page would bypass that gate.
- Secrets stay on the server. Client secrets are never shipped in Flutter web bundles or the CLI.
- One place to change. Clients only call /api/auth/*, so a provider change needs no new dashboard or CLI releases.
What the backend does today
- Session: login, refresh token, logout (revokes the refresh token).
- Token validation: local check, then introspection, then userinfo.
- Users: create with credentials, get by email, delete, set password, update attributes, clear required actions.
- Password reset: send a reset email for the linked user.
- Roles (admin API): list, grant and remove client roles, list a role's users.
Both dart_cloud_backend and admin_backend_api use the concrete KeycloakClient from the auth-keycloak-client package. The Postgres column keycloak_user_id links a local user to the provider user.
Trade-offs of this design
- Passwords pass through the backend, and the flow is a direct password grant rather than a redirect to a provider login page.
- No provider SSO or hosted login screen.
- The backend owns session handling, token validation and registration logic.
Planned: IdentityProvider interface
Goal: switching provider means writing a new implementation and changing one setting, with no changes to handlers, middleware or clients.
- Interface. An IdentityProvider in the auth package covering the calls above, using provider-neutral token and user types.
- Keycloak implementation. KeycloakIdentityProvider wraps the current client. Both backends depend only on the interface.
- Selection. An AUTH_PROVIDER setting picks the implementation at startup.
- Neutral identity column. Rename keycloak_user_id to a provider-neutral name (for example external_auth_id) with a migration.
- Own the roles. Feature roles and admin permissions are currently Keycloak client roles, which other providers do not model the same way. Store roles in our database and use the provider only for identity.
Owning the roles is the hardest part of leaving Keycloak. Doing it before a migration keeps the provider swap small.