{"record":{"id":"09e938f910105596","repo":"danny-avila/LibreChat","slug":"graph-token-acquisition-failed-error-message","errorCode":null,"errorMessage":"Graph token acquisition failed: ${error.message}","messagePattern":"Graph token acquisition failed: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"api/server/services/GraphTokenService.js","lineNumber":22,"sourceCode":"/**\n * Get Microsoft Graph API token using the On-Behalf-Of flow.\n * Thin wrapper around the generic OBO exchange for Graph-specific error context.\n *\n * @param {Object} user - User object with OpenID information\n * @param {string} accessToken - Federated access token used as OBO assertion\n * @param {string} scopes - Graph API scopes for the token\n * @param {boolean} [fromCache=true] - Whether to try getting token from cache first\n * @returns {Promise<Object>} Graph API token response with access_token and expires_in\n */\nasync function getGraphApiToken(user, accessToken, scopes, fromCache = true) {\n  try {\n    return await exchangeOboToken(user, accessToken, scopes, fromCache);\n  } catch (error) {\n    logger.error(\n      `[GraphTokenService] Failed to acquire Graph API token for user ${user.openidId}:`,\n      error,\n    );\n    throw new Error(`Graph token acquisition failed: ${error.message}`);\n  }\n}\n\nmodule.exports = {\n  getGraphApiToken,\n};\n","sourceCodeStart":4,"sourceCodeEnd":29,"githubUrl":"https://github.com/danny-avila/LibreChat/blob/5ff282f9006c436e561de1afd39a481bea1ef0d8/api/server/services/GraphTokenService.js#L4-L29","documentation":"getGraphApiToken() in GraphTokenService.js:22 wraps any failure of exchangeOboToken and rethrows with this message, preserving the original error.message. The wrapper exists to add Graph-specific context and log the openidId. The underlying cause is almost always an Entra/Azure AD OBO (jwt-bearer) exchange failure: bad assertion, missing consent, wrong scopes, or a transient IdP error.","triggerScenarios":"An MCP tool or Graph-dependent flow calls getGraphApiToken; exchangeOboToken rejects because the user's access token is expired, the requested scopes are not consented, the IdP returned 401/invalid_grant, or a transient 429/503 exhausted its single retry.","commonSituations":"OpenID/OBO misconfigured in librechat.yaml (wrong tenant, missing client secret). User lacks admin consent for the downstream scopes. Token cache served a stale token and the IdP rejected it. Network blip hitting the one retry budget.","solutions":["Read the wrapped error.message — it carries the IdP's reason (invalid_grant, consent_required, etc.).","If invalid_grant/expired token: force a fresh sign-in so a new access token is minted, then retry.","If consent_required: grant admin consent for the requested Graph scopes in the Entra app registration.","Verify OPENID_ISSUER, client id/secret, and OBO scopes in librechat.yaml match the app registration."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"function assertOboInputs(user, accessToken, scopes) {\n  if (!user?.openidId) throw new Error('openidId required');\n  if (!accessToken) throw new Error('accessToken required');\n  if (!scopes) throw new Error('scopes required');\n}","typeGuard":"const hasOboInputs = (user, token, scopes) => !!user?.openidId && !!token && !!scopes;","tryCatchPattern":"try {\n  const token = await getGraphApiToken(user, accessToken, scopes);\n} catch (e) {\n  if (/invalid_grant|expired/.test(e.message)) { await refreshUserSession(); return retry(); }\n  if (/consent_required/.test(e.message)) { return promptAdminConsent(scopes); }\n  throw e;\n}","preventionTips":["Verify Entra app registration scopes and admin consent before enabling Graph-backed features.","Log the wrapped error.message — it carries the IdP's specific reason.","Confirm librechat.yaml OPENID settings match the app registration after each env change."],"tags":["mcp","openid","oauth","azure-ad","tokens","network"],"backgroundTag":null,"analyzedSha":"5ff282f9006c436e561de1afd39a481bea1ef0d8","analyzedAt":"2026-08-12T21:38:08.145Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}