{"record":{"id":"4a1875495c5382bc","repo":"Budibase/budibase","slug":"could-not-determine-user-email-from-profile-json","errorCode":null,"errorMessage":"Could not determine user email from profile ${JSON.stringify(profile)} and claims ${JSON.stringify(jwtClaims)}","messagePattern":"Could not determine user email from profile (.+?) and claims (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/backend-core/src/middleware/passport/sso/oidc.ts","lineNumber":150,"sourceCode":" */\nfunction getEmail(profile: SSOProfile, jwtClaims: JwtClaims) {\n  // profile not guaranteed to contain email e.g. github connected azure ad account\n  if (profile._json.email) {\n    return profile._json.email.toLowerCase()\n  }\n\n  // fallback to id token email\n  if (jwtClaims.email) {\n    return jwtClaims.email.toLowerCase()\n  }\n\n  // fallback to id token preferred username\n  const username = jwtClaims.preferred_username\n  if (username && validEmail(username)) {\n    return username.toLowerCase()\n  }\n\n  throw new Error(\n    `Could not determine user email from profile ${JSON.stringify(\n      profile\n    )} and claims ${JSON.stringify(jwtClaims)}`\n  )\n}\n\n/**\n * Determines whether the identity provider has verified the email that\n * getEmail resolved. Mirrors getEmail's source precedence so the returned flag\n * describes the same claim. An absent email_verified is treated as unverified\n * (OIDC Core §5.7). A preferred_username used as an email is never considered\n * verified.\n * @param profile The structured profile created by passport using the user info endpoint\n * @param jwtClaims The claims returned in the id token\n */\nfunction getEmailVerified(profile: SSOProfile, jwtClaims: JwtClaims): boolean {\n  if (profile._json.email) {\n    return profile._json.email_verified === true","sourceCodeStart":132,"sourceCodeEnd":168,"githubUrl":"https://github.com/Budibase/budibase/blob/a81a902e9a8fe55b467d106765f6638f12e35c49/packages/backend-core/src/middleware/passport/sso/oidc.ts#L132-L168","documentation":"getEmail resolves the user's email during OIDC SSO verification from the profile, then jwtClaims.email, then jwtClaims.preferred_username (if it is a valid email). If none of these yield a usable email it throws this error because Budibase requires an email identity to save/link the SSO user.","triggerScenarios":"An OIDC IdP completes authentication but returns a profile and ID token with no email claim, no valid email-formatted claim values, and no preferred_username that parses as an email.","commonSituations":"IdPs that only return a subject ID or opaque username (e.g. username 'jdoe' with no @domain); email scope not requested/granted in the OIDC client config; IdP admin has not populated emails for users; custom enterprise IdPs with nonstandard claims.","solutions":["Configure the OIDC provider to release the email scope and the email claim in the ID token/userinfo","Set preferred_username to a valid email address on the IdP, or map a claim containing the user's email into preferred_username","Check the profile JSON in the message to see exactly which claims the IdP is returning and adjust claim mapping","If the IdP cannot provide emails, use a different authentication method or a connector that synthesizes emails"],"exampleFix":"// before (IdP returns preferred_username: \"jdoe\")\n// after: configure claim mapping so preferred_username = \"jdoe@example.com\" (or add scope \"email\")","handlingStrategy":"validation","validationCode":"function hasUsableEmail(profile, jwtClaims) {\n  const candidates = [profile?.email, jwtClaims?.email, jwtClaims?.preferred_username]\n  return candidates.some(c => typeof c === \"string\" && /^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$/.test(c))\n}","typeGuard":"function isEmail(value) {\n  return typeof value === \"string\" && /^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$/.test(value)\n}","tryCatchPattern":"try {\n  await strategy.authenticate(req)\n} catch (err) {\n  if (String(err.message).startsWith(\"Could not determine user email\")) {\n    // surface a friendly 'your identity provider does not share your email' message\n  }\n}","preventionTips":["Always request the 'email' (and 'profile') scopes in the OIDC client configuration","Confirm with the IdP admin that email and preferred_username claims are released to your client","Test login with a real IdP account before rollout and inspect returned claims","Provide a claim mapping so an email-bearing claim populates preferred_username if needed"],"tags":["sso","oidc","email","claims"],"backgroundTag":"sso-missing-email-claim","analyzedSha":"a81a902e9a8fe55b467d106765f6638f12e35c49","analyzedAt":"2026-08-29T01:03:10.972Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}