Aller au contenu

ADR-038 — OIDC user matching by sub claim, not email

Status: PROPOSED — implementation pending Date: 2026-04-27 Sprint: 54 (debt captured), implementation candidate Sprint 55+

Context

Aujourd'hui (2026-04-27) on a hit un bug live qui s'est répété sur 2 apps :

  • OpenMetadata (catalog.akko-ai.com) : boucle infinie /signin ↔ /auth/callback
  • Superset (bi.akko-ai.com) : aurait eu le même bug, prévenu via SQL UPDATE

Cause racine : les users en DB de chaque app ont un email seed (alice@159.195.77.208.nip.io) qui ne matche plus le JWT publié par Keycloak (alice@akko-ai.com). L'app rejette le login avec un mismatch d'email malgré un username identique.

Notre fix de jour : SQL UPDATE des emails + helm hook qui re-aligne à chaque upgrade. C'est un bricolage. Si l'email change pour n'importe quelle raison (user édite son email côté Keycloak, fédération SCIM, migration domaine) → ça recasse.

Decision

Adopter sub (Keycloak UUID) comme clé de matching primaire, conformément à OpenID Connect Core 1.0 §2 :

The sub Claim, combined with iss, is the only Claim that an RP can rely upon as a stable identifier for the End-User. The sub value MUST be locally unique and never reassigned.

L'email devient un attribut métier (peut changer), pas la clé primaire.

Consequences

Per-app implementation cost

App Where Cost Risk
OpenMetadata 1.12 jwtPrincipalClaims config 1 ligne YAML + migration des users existants pour stocker leur sub Keycloak RISQUE de doubles users si bascule à chaud sans migration. À tester en sandbox avant prod.
Superset 4.1 (FAB) Custom SecurityManager Python override ~50 lignes Python qui surchargent auth_user_oauth pour matcher par sub au lieu de username/email Code custom à maintenir. Pas de config knob FAB natif.
Airflow 3.x (FAB) Idem Superset ~50 lignes Python Idem

Migration path

Pour chaque app :

  1. Ajouter une colonne external_id (ou champ JSON) qui stocke le sub Keycloak.
  2. Backfill : pour chaque user existant, lookup son sub via Keycloak admin API (par username), update la row.
  3. Changer le matching code pour utiliser external_id en priorité, fallback sur username/email pour les users pas encore backfillés.
  4. Tests E2E : login alice avec email change côté IdP → doit toujours marcher.

Pattern enterprise alternatif (SCIM 2.0)

À grande échelle (Dremio, DataHub, Snowflake), l'IdP push les changes user via SCIM. AKKO a déjà un scim-bot user en DB OpenMetadata mais SCIM n'est pas wiré.

Coût SCIM : plusieurs jours par app (extension Keycloak SCIM + endpoint SCIM côté chaque app).

Sources

Current debt (workaround in place 2026-04-27)

  • PR #118 : Helm init job qui SQL UPDATE les emails OM user_entity.json->>email pour les aligner sur ${AKKO_DOMAIN} à chaque helm upgrade.
  • SQL UPDATE manuel sur Superset ab_user (alice + admin → @akko-ai.com). PAS de hook Helm équivalent — symétrie volontairement non-poussée pour ne pas amplifier le bricolage.

Cette dette est tracée comme ADR-038 implementation dans le backlog Sprint 55+.

Decision pending founder

  • Investir 3-5 jours sur l'implémentation sub-based matching (3 apps) ?
  • OU rester sur le hook re-align tant que le domaine ne change plus (acceptable risque) ?
  • OU passer directement à SCIM 2.0 (~10 jours, vraie solution enterprise) ?