Microsoft-login faalt met 400 "email: Cannot be blank" — Graph userinfo levert geen e-mailclaim voor accounts zonder mail-attribuut #24

Closed
opened 2026-07-12 15:35:03 +00:00 by rve · 0 comments
Owner

Symptoom

Na de fix van #22 faalt de Microsoft-login met een nieuwe fout:

{"data":{"email":{"code":"validation_required","message":"Cannot be blank."}},"message":"Failed to create record.","status":400}

Root cause (gereproduceerd met mock-Entra, PocketBase v0.30.4)

De provider haalt de gebruikersclaims op via Graph https://graph.microsoft.com/oidc/userinfo. Dat endpoint geeft de email-claim alleen terug als het account een mail-attribuut heeft (Exchange-mailbox). Voor accounts zonder mail-attribuut (gebruikelijk in lab-/testtenants) ontbreekt de claim → PocketBase maakt het record aan met lege e-mail → email is verplicht op team_members → 400 "Failed to create record".

De fingerprint-matrix uit de analyse (token-fail / userinfo-fail / e-mail-ontbreekt) matcht exact op de derde variant; de door de gebruiker aangeleverde response-body is identiek aan de lokaal gereproduceerde.

Fix (twee lagen, beide nodig voor robuustheid)

  1. Claims uit het id_token i.p.v. Graph-userinfo (userInfoURL: "" in de provider-config). Het Entra v2 id_token bevat naast email (indien aanwezig) áltijd preferred_username — de UPN, bij Respellion gelijk aan het primaire e-mailadres. Empirisch geverifieerd: PocketBase mapt preferred_usernameusername en houdt de volledige claims beschikbaar in rawUser. De provider-config gaat via de #18-reconciler, dus Labs flipt automatisch bij de eerstvolgende deploy-boot — geen migratie nodig.
  2. UPN-fallback in een onRecordAuthWithOAuth2Request-hook: als de email-claim leeg is en preferred_username een geldig e-mailadres is (regex-guard; guest-UPN's met #EXT# vallen er bewust buiten), wordt die als e-mail gebruikt (lowercased — matcht de ENTRA_ADMIN_EMAILS-allow-list). De hook logt bovendien elke gefaalde OAuth2-poging met de onderliggende fout naar de containerlog — de observability die we bij deze debugsessie misten.

Lokaal bewezen: id_token zonder e-mailclaim → record met email=rve@respellion.nl, role=admin via allow-list, onboarding-defaults gezet.

Verificatie

  • Repro: id_token/userinfo zonder e-mail → exact de productie-400
  • Fix: zelfde scenario → 200, e-mail uit UPN, role/enrollment correct
  • Volledige matrix na implementatie (met/zonder e-mailclaim, ongeldig UPN → nette fout, regressie #22-matrix + #18-harness)
  • E2E op Labs met echt Microsoft-account

Alternatief overwogen en afgewezen

Mail-attribuut zetten op de Entra-accounts lost het voor dít account op, maar laat de app fragiel voor elk volgend account zonder mailbox; de UPN-fallback maakt de login deterministisch voor alle organisatie-accounts.

Gerelateerd: #16, #18, #20, #22 (PR #23 — nog niet gemerged; deze fix stapelt daarop).

## Symptoom Na de fix van #22 faalt de Microsoft-login met een nieuwe fout: ```json {"data":{"email":{"code":"validation_required","message":"Cannot be blank."}},"message":"Failed to create record.","status":400} ``` ## Root cause (gereproduceerd met mock-Entra, PocketBase v0.30.4) De provider haalt de gebruikersclaims op via **Graph `https://graph.microsoft.com/oidc/userinfo`**. Dat endpoint geeft de `email`-claim **alleen** terug als het account een mail-attribuut heeft (Exchange-mailbox). Voor accounts zonder mail-attribuut (gebruikelijk in lab-/testtenants) ontbreekt de claim → PocketBase maakt het record aan met lege e-mail → `email` is verplicht op `team_members` → 400 "Failed to create record". De fingerprint-matrix uit de analyse (token-fail / userinfo-fail / e-mail-ontbreekt) matcht exact op de derde variant; de door de gebruiker aangeleverde response-body is identiek aan de lokaal gereproduceerde. ## Fix (twee lagen, beide nodig voor robuustheid) 1. **Claims uit het id_token i.p.v. Graph-userinfo** (`userInfoURL: ""` in de provider-config). Het Entra v2 id_token bevat naast `email` (indien aanwezig) áltijd `preferred_username` — de UPN, bij Respellion gelijk aan het primaire e-mailadres. Empirisch geverifieerd: PocketBase mapt `preferred_username` → `username` en houdt de volledige claims beschikbaar in `rawUser`. De provider-config gaat via de #18-reconciler, dus Labs flipt automatisch bij de eerstvolgende deploy-boot — geen migratie nodig. 2. **UPN-fallback in een `onRecordAuthWithOAuth2Request`-hook**: als de `email`-claim leeg is en `preferred_username` een geldig e-mailadres is (regex-guard; guest-UPN's met `#EXT#` vallen er bewust buiten), wordt die als e-mail gebruikt (lowercased — matcht de `ENTRA_ADMIN_EMAILS`-allow-list). De hook logt bovendien elke gefaalde OAuth2-poging met de onderliggende fout naar de containerlog — de observability die we bij deze debugsessie misten. Lokaal bewezen: id_token zonder e-mailclaim → record met `email=rve@respellion.nl`, `role=admin` via allow-list, onboarding-defaults gezet. ## Verificatie - [x] Repro: id_token/userinfo zonder e-mail → exact de productie-400 - [x] Fix: zelfde scenario → 200, e-mail uit UPN, role/enrollment correct - [ ] Volledige matrix na implementatie (met/zonder e-mailclaim, ongeldig UPN → nette fout, regressie #22-matrix + #18-harness) - [ ] E2E op Labs met echt Microsoft-account ## Alternatief overwogen en afgewezen Mail-attribuut zetten op de Entra-accounts lost het voor dít account op, maar laat de app fragiel voor elk volgend account zonder mailbox; de UPN-fallback maakt de login deterministisch voor alle organisatie-accounts. Gerelateerd: #16, #18, #20, #22 (PR #23 — nog niet gemerged; deze fix stapelt daarop).
rve closed this issue 2026-07-12 19:04:30 +00:00
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: rve/learning-platform#24