danny-avila/LibreChat · error · Error

You must have ${rolesList} role to log in.

Error message

You must have ${rolesList} role to log in.

What it means

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.

Source

Thrown at api/strategies/openidStrategy.js:659

      decodedToken &&
      hasOverage
    ) {
      const overageGroups = await resolveGroupsFromOverage(tokenset.access_token, claims.sub);
      if (overageGroups) {
        roles = overageGroups;
        resolvedOverageGroups = overageGroups;
      }
    }

    if (!roles || (!Array.isArray(roles) && typeof roles !== 'string')) {
      logger.error(
        `[openidStrategy] Key '${requiredRoleParameterPath}' not found in ${requiredRoleTokenKind} token!`,
      );
      const rolesList =
        requiredRoles.length === 1
          ? `"${requiredRoles[0]}"`
          : `one of: ${requiredRoles.map((r) => `"${r}"`).join(', ')}`;
      throw new Error(`You must have ${rolesList} role to log in.`);
    }

    const roleValues = Array.isArray(roles) ? roles : roles.split(/[\s,]+/).filter(Boolean);

    if (!requiredRoles.some((role) => roleValues.includes(role))) {
      const rolesList =
        requiredRoles.length === 1
          ? `"${requiredRoles[0]}"`
          : `one of: ${requiredRoles.map((r) => `"${r}"`).join(', ')}`;
      throw new Error(`You must have ${rolesList} role to log in.`);
    }
  }

  let username = '';
  if (process.env.OPENID_USERNAME_CLAIM) {
    username = userinfo[process.env.OPENID_USERNAME_CLAIM];
  } else {
    username = convertToUsername(

View on GitHub (pinned to 5ff282f900)

Solutions

  1. 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.
  2. 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.
  3. On the IdP side, ensure the user/group is assigned the app role that emits the claim (Azure AD: app roles + user/group assignment).
  4. If the claim legitimately may be empty for some users, decide whether absence should deny (current behavior) and document it.
Defensive patterns

Strategy: validation

Validate before calling

// Decode the relevant token and confirm the claim path exists before enabling role gating
function findRoleClaim(token, path, kind) {
  const decoded = jwt.decode(token);
  return kind === 'access_token' ? decoded?.[path] : decoded?.[path];
}
const roles = findRoleClaim(idToken, process.env.OPENID_REQUIRED_ROLE_PARAMETER_PATH, process.env.OPENID_REQUIRED_ROLE_TOKEN_KIND);
if (!roles || (!Array.isArray(roles) && typeof roles !== 'string')) {
  throw new Error(`Role claim '${process.env.OPENID_REQUIRED_ROLE_PARAMETER_PATH}' not found in ${process.env.OPENID_REQUIRED_ROLE_TOKEN_KIND}`);
}

Type guard

function isRoleClaim(v: unknown): v is string | string[] {
  return typeof v === 'string' || (Array.isArray(v) && v.every((r) => typeof r === 'string'));
}

Try / catch

try {
  // role enforcement block
} catch (err) {
  if (err.message.includes('role to log in')) return done(null, false, { message: err.message });
  throw err;
}

Prevention

When it happens

Trigger: `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.

Common situations: 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.

Related errors


AI-assisted analysis of danny-avila/LibreChat@5ff282f900 (2026-08-12). Data as JSON: /api/errors/a9b074825f56488b. Report an issue: GitHub.