Id RFC 009 migration from zitadel
RFC-009 — Migration from Zitadel
- *tatus:*Draft
- *ate:*2026
0408 - *uthor:*Koder Team
- *epends on:*RFC
001 through RFC008
Summary
Defines the strategy to migrate the current Zitadel-based Koder ID to the new implementation without downtime for existing users and integrated products (Flow, Talk, etc.).
Current State
- *itadel*running on k.lin (127.0.0.1:8090 behind koder-jet)
- *RL:*https:/d.koder.dev
- *enants:*Koder, Crescer, Vivver (as Zitadel organizations)
- *ntegrated products:*Koder Flow (Gitea), Koder Talk (in development)
- *sers:*admin (root), platform-admin, and org users
- *rotocols:*OIDC, OAuth2
Migration Strategy: Parallel Run
Phase 1: Build Phase 2: Shadow Phase 3: Cutover
────────────────── ────────────────── ──────────────────
New system in dev New system receives New system is
No production traffic mirrored auth requests primary
Zitadel still primary Zitadel shutdownPhase 1 — Build (current)
- Implement all microservices per RFCs 001-008
- Write comprehensive tests (unit, integration, e2e)
- Deploy on staging environment
- Run OIDC conformance tests
*xit criteria:*All OIDC conformance tests pass, e2e tests cover all integrated products.
Phase 2 — Data Migration & Shadow
- *xport from Zitadel:*
- Users (profiles, email, status)
- Organizations → tenants
- OAuth2 clients (client_id, redirect URIs, scopes)
- NOTE: password hashes cannot be exported from Zitadel (bcrypt internal)
- *assword migration strategy:*
- Option A: Force password reset for all users on cutover
- Option B: Proxy login to Zitadel during transition — on successful login, re-hash password with Argon2id in new system
- *ecommended: Option B*— transparent to users, no disruption
- *hadow mode:*
- Deploy new system alongside Zitadel
- Gateway proxies auth requests to both systems
- Zitadel responses are authoritative
- New system responses are logged and compared (but not served to clients)
- Fix discrepancies until shadow results match 100%
Phase 3 — Cutover
- Update DNS/proxy to route to new system
- Keep Zitadel running in read-only mode for 7 days (rollback safety net)
- Monitor error rates, auth success rates, latency
- After 7 days with no issues, decommission Zitadel
*utover is reversible:*if issues arise, switch DNS back to Zitadel within minutes.
Data Mapping
Zitadel → Koder ID v2
| Zitadel Concept | Koder ID v2 | Table |
|---|---|---|
| Organization | Tenant | tenants |
| Human User | User | users |
| Machine User | API Key | api_keys |
| Application (OIDC) | OAuth2 Client | clients |
| Project | (no direct equivalent) | — |
| Grant | Consent | consents |
| Session | Session | sessions |
| Personal Access Token | API Key | api_keys |
Migration Script Outline
func MigrateFromZitadel(zitadelDB *sql.DB, koderID *AdminClient) error {
// 1. Migrate organizations → tenants
orgs, _ := exportZitadelOrgs(zitadelDB)
for _, org := range orgs {
koderID.CreateTenant(org.Name, org.Domain)
}
// 2. Migrate users (without passwords)
users, _ := exportZitadelUsers(zitadelDB)
for _, user := range users {
koderID.CreateUser(user.Email, user.DisplayName, user.OrgID)
// Password will be migrated on first login (Phase 2, Option B)
}
// 3. Migrate OIDC applications → OAuth2 clients
apps, _ := exportZitadelApps(zitadelDB)
for _, app := range apps {
koderID.RegisterClient(app.Name, app.ClientID, app.RedirectURIs, app.Scopes)
}
return nil
}Product Integration Updates
Each product needs minimal changes to point to the new OIDC endpoints:
| Product | Change Required |
|---|---|
| Koder Flow (Gitea) | Update OIDC discovery URL (same if domain stays id.koder.dev) |
| Koder Talk | Update OIDC config (in development, easy) |
| KDB Dashboard | Update OIDC config |
If the OIDC discovery URL remains https://id.koder.dev/.well-known/openid-configuration, most products require *ero changes*— they discover endpoints dynamically.
Rollback Plan
At any point during Phase 2 or 3:
- Stop new system's gateway
- Point DNS/proxy back to Zitadel
- Zitadel has all original data (read-only mode still running)
- Users may need to re-login (sessions won't carry over)
Timeline Estimate
| Phase | Duration |
|---|---|
| Phase 1 — Build | Core development |
| Phase 2 — Shadow | 2 weeks minimum |
| Phase 3 — Cutover | 1 day + 7 days monitoring |
Risks
| Risk | Mitigation |
|---|---|
| Password hashes not exportable | Transparent re-hash on login (Option B) |
| OIDC spec compliance gaps | Run official conformance test suite |
| KDB instability | PostgreSQL fallback driver ready |
| Service discovery misconfiguration | Health check endpoint catches issues early |
Open Questions
- Should we keep Zitadel's user IDs or generate new ones? (keeping them avoids breaking external references)
- How to handle Zitadel's event sourcing data? (export aggregate state, not events)
- MFA devices — can TOTP secrets be exported from Zitadel, or do users need to re-register?