{"record":{"id":"32939e0fe6052623","repo":"medusajs/medusa","slug":"the-identity-provider-s-id-token-is-missing-the","errorCode":null,"errorMessage":"The identity provider's ID token is missing the '${entityIdClaim}' claim used to identify the user","messagePattern":"The identity provider's ID token is missing the '(.+?)' claim used to identify the user","errorType":"exception","errorClass":"MedusaError","httpStatus":400,"severity":"error","filePath":"packages/modules/providers/auth-oidc/src/engine/engine.ts","lineNumber":220,"sourceCode":"   *\n   * `entity_id` defaults to the `sub` claim (never the email, which is mutable\n   * and reassignable).\n   */\n  mapClaims(claims: Record<string, unknown>): OidcMappedClaims {\n    const mappings: OidcClaimMappings = {\n      ...DEFAULT_CLAIM_MAPPINGS,\n      ...this.options_.claim_mappings,\n    }\n\n    const entityIdClaim = mappings.entity_id ?? \"sub\"\n    const entityIdValue = claims[entityIdClaim]\n\n    if (\n      entityIdValue === undefined ||\n      entityIdValue === null ||\n      entityIdValue === \"\"\n    ) {\n      throw new MedusaError(\n        MedusaError.Types.INVALID_DATA,\n        `The identity provider's ID token is missing the '${entityIdClaim}' claim used to identify the user`\n      )\n    }\n\n    const emailClaim = mappings.email ?? \"email\"\n    const email = claims[emailClaim]\n\n    const requireVerifiedEmail = this.options_.require_verified_email ?? true\n    if (requireVerifiedEmail && claims.email_verified !== true) {\n      throw new MedusaError(\n        MedusaError.Types.INVALID_DATA,\n        \"The identity provider did not confirm a verified email address\"\n      )\n    }\n\n    if (this.options_.allowed_email_domains?.length) {\n      const allowedDomains = this.options_.allowed_email_domains.map((domain) =>","sourceCodeStart":202,"sourceCodeEnd":238,"githubUrl":"https://github.com/medusajs/medusa/blob/5e06e544a296b9033f20f71f11c559f81a0e5739/packages/modules/providers/auth-oidc/src/engine/engine.ts#L202-L238","documentation":"mapClaims extracts the user identifier from a configurable claim (mappings.entity_id, defaulting typically to 'sub'). If that claim is absent, null, or empty string in the ID token, the engine cannot identify the user and throws INVALID_DATA naming the missing claim.","triggerScenarios":"mappings.entity_id is set to a claim the IdP does not emit (e.g. 'preferred_username' on a provider that doesn't include it in the ID token); the claim only appears in the userinfo endpoint, not the ID token; scopes needed to include the claim were not requested so the claim is empty.","commonSituations":"Copied claim mappings from a different IdP (Azure AD vs Keycloak vs Google emit different claims); custom claim configured in Keycloak but not added to the ID token mapper; requested scopes don't cover the claim (e.g. email claim without 'email' scope).","solutions":["Decode the ID token (e.g. jwt.io or JSON.parse(Buffer.from(token.split('.')[1], 'base64'))) and check which claims are actually present.","Either set mappings.entity_id to a claim that exists (commonly 'sub' or 'email'), or configure the IdP to include the desired claim in the ID token.","Request the scopes (e.g. 'email', 'profile') that make the IdP emit the claim."],"exampleFix":"// before\noptions: { ..., mappings: { entity_id: \"preferred_username\" } }\n// after\noptions: { ..., mappings: { entity_id: \"sub\" } }","handlingStrategy":"validation","validationCode":"function decodeJwtClaims(idToken: string): Record<string, unknown> {\n  const payload = idToken.split(\".\")[1]\n  return JSON.parse(Buffer.from(payload, \"base64url\").toString(\"utf8\"))\n}\nconst claims = decodeJwtClaims(idToken)\nif (claims[mappings.entity_id ?? \"sub\"] == null) throw new Error(\"entity_id claim missing in ID token\")","typeGuard":"const hasEntityIdClaim = (claims: Record<string, unknown>, claim: string): boolean =>\n  claims[claim] !== undefined && claims[claim] !== null && claims[claim] !== \"\"","tryCatchPattern":"try { engine.mapClaims(claims) } catch (e) { if (e instanceof MedusaError && /missing the '.*' claim/.test(e.message)) { /* adjust mappings or IdP claim config */ } throw e }","preventionTips":["Map entity_id to 'sub' unless you have verified the custom claim appears in the ID token.","Decode a sample ID token when onboarding a new IdP.","Request scopes that unlock the claims you map (email, profile)."],"tags":["oidc","claims","id-token","mapping"],"backgroundTag":"oidc-missing-claim","analyzedSha":"5e06e544a296b9033f20f71f11c559f81a0e5739","analyzedAt":"2026-08-27T07:24:39.599Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}