{"record":{"id":"a9b074825f56488b","repo":"danny-avila/LibreChat","slug":"you-must-have-roleslist-role-to-log-in","errorCode":null,"errorMessage":"You must have ${rolesList} role to log in.","messagePattern":"You must have (.+?) role to log in\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"api/strategies/openidStrategy.js","lineNumber":659,"sourceCode":"      decodedToken &&\n      hasOverage\n    ) {\n      const overageGroups = await resolveGroupsFromOverage(tokenset.access_token, claims.sub);\n      if (overageGroups) {\n        roles = overageGroups;\n        resolvedOverageGroups = overageGroups;\n      }\n    }\n\n    if (!roles || (!Array.isArray(roles) && typeof roles !== 'string')) {\n      logger.error(\n        `[openidStrategy] Key '${requiredRoleParameterPath}' not found in ${requiredRoleTokenKind} token!`,\n      );\n      const rolesList =\n        requiredRoles.length === 1\n          ? `\"${requiredRoles[0]}\"`\n          : `one of: ${requiredRoles.map((r) => `\"${r}\"`).join(', ')}`;\n      throw new Error(`You must have ${rolesList} role to log in.`);\n    }\n\n    const roleValues = Array.isArray(roles) ? roles : roles.split(/[\\s,]+/).filter(Boolean);\n\n    if (!requiredRoles.some((role) => roleValues.includes(role))) {\n      const rolesList =\n        requiredRoles.length === 1\n          ? `\"${requiredRoles[0]}\"`\n          : `one of: ${requiredRoles.map((r) => `\"${r}\"`).join(', ')}`;\n      throw new Error(`You must have ${rolesList} role to log in.`);\n    }\n  }\n\n  let username = '';\n  if (process.env.OPENID_USERNAME_CLAIM) {\n    username = userinfo[process.env.OPENID_USERNAME_CLAIM];\n  } else {\n    username = convertToUsername(","sourceCodeStart":641,"sourceCodeEnd":677,"githubUrl":"https://github.com/danny-avila/LibreChat/blob/5ff282f9006c436e561de1afd39a481bea1ef0d8/api/strategies/openidStrategy.js#L641-L677","documentation":"Thrown inside the `OPENID_REQUIRED_ROLE` enforcement block when the configured role claim is entirely absent or the wrong type. The strategy locates the claim at `requiredRoleParameterPath` inside the token identified by `requiredRoleTokenKind` (id_token or access_token). If the result `roles` is falsy, or is neither an array nor a string, the strategy logs that the path was not found and throws. The message lists the required role(s). Mapped by the callback to `done(null, false, { message })` → `auth_failed` redirect.","triggerScenarios":"`OPENID_REQUIRED_ROLE` is set (e.g. `admin`) and `OPENID_REQUIRED_ROLE_PARAMETER_PATH`/`OPENID_REQUIRED_ROLE_TOKEN_KIND` point at a claim the IdP does not emit for this user — e.g. Azure AD without the `roles` directory-role claim, or an IdP that puts groups in `groups` rather than `roles`, or a user whose app-role assignment is empty. Also when the claim exists but is an object/number rather than array/string.","commonSituations":"Enabling role-gated login for the first time without confirming the IdP actually issues the claim; Azure AD app registration missing the 'app roles' assignment; `OPENID_REQUIRED_ROLE_PARAMETER_PATH` typo (e.g. `role` vs `roles`); the claim lives in the access_token but `OPENID_REQUIRED_ROLE_TOKEN_KIND` defaults to the id_token.","solutions":["Decode the id_token and access_token (e.g. jwt.io) for the failing user and confirm which token actually carries the role claim and its exact JSON path.","Set `OPENID_REQUIRED_ROLE_TOKEN_KIND` to the token (`id_token` or `access_token`) that contains the claim, and `OPENID_REQUIRED_ROLE_PARAMETER_PATH` to the exact key.","On the IdP side, ensure the user/group is assigned the app role that emits the claim (Azure AD: app roles + user/group assignment).","If the claim legitimately may be empty for some users, decide whether absence should deny (current behavior) and document it."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Decode the relevant token and confirm the claim path exists before enabling role gating\nfunction findRoleClaim(token, path, kind) {\n  const decoded = jwt.decode(token);\n  return kind === 'access_token' ? decoded?.[path] : decoded?.[path];\n}\nconst roles = findRoleClaim(idToken, process.env.OPENID_REQUIRED_ROLE_PARAMETER_PATH, process.env.OPENID_REQUIRED_ROLE_TOKEN_KIND);\nif (!roles || (!Array.isArray(roles) && typeof roles !== 'string')) {\n  throw new Error(`Role claim '${process.env.OPENID_REQUIRED_ROLE_PARAMETER_PATH}' not found in ${process.env.OPENID_REQUIRED_ROLE_TOKEN_KIND}`);\n}","typeGuard":"function isRoleClaim(v: unknown): v is string | string[] {\n  return typeof v === 'string' || (Array.isArray(v) && v.every((r) => typeof r === 'string'));\n}","tryCatchPattern":"try {\n  // role enforcement block\n} catch (err) {\n  if (err.message.includes('role to log in')) return done(null, false, { message: err.message });\n  throw err;\n}","preventionTips":["Before enabling OPENID_REQUIRED_ROLE, decode a real user's id_token AND access_token and confirm which carries the claim and its exact path.","Set OPENID_REQUIRED_ROLE_TOKEN_KIND and OPENID_REQUIRED_ROLE_PARAMETER_PATH to match what the IdP actually emits.","Grant the app role to at least one admin user in the IdP before turning gating on."],"tags":["authentication","openid","rbac","role-gating","claims","config"],"backgroundTag":null,"analyzedSha":"5ff282f9006c436e561de1afd39a481bea1ef0d8","analyzedAt":"2026-08-12T21:38:08.145Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}