danny-avila/LibreChat · error · Error
User must be authenticated via OpenID to perform OBO token e
Error message
User must be authenticated via OpenID to perform OBO token exchange
What it means
exchangeOboToken() in OboTokenService.js:128 throws this as its first validation guard: the OBO (jwt-bearer) flow requires an OpenID-authenticated user, identified by user.openidId. Without an openidId the service cannot build the cache key or assert the federated identity to the IdP, so it refuses immediately — before checking accessToken or scopes.
Source
Thrown at api/server/services/OboTokenService.js:128
/**
* Exchange a user's access token for a downstream-scoped token via the
* OAuth 2.0 On-Behalf-Of (jwt-bearer) grant.
*
* Concurrent callers for the same `${openidId}:${scopes}` key share a single
* upstream exchange (see `inFlightExchanges`) so a fan-out of tool calls right
* after a cache miss does not produce N parallel requests to the IdP.
*
* @param {Object} user - User object with OpenID information
* @param {string} accessToken - Federated access token used as OBO assertion
* @param {string} scopes - Scopes to request for the downstream service
* @param {boolean} [fromCache=true] - When true, read from cache and join any
* in-flight exchange. When false, bypass both and force a fresh exchange.
* @returns {Promise<Object>} Token response with access_token and expires_in
*/
async function exchangeOboToken(user, accessToken, scopes, fromCache = true) {
if (!user.openidId) {
throw new Error('User must be authenticated via OpenID to perform OBO token exchange');
}
if (!accessToken) {
throw new Error('Access token is required for OBO exchange');
}
if (!scopes) {
throw new Error('Scopes are required for OBO exchange');
}
const config = getOpenIdConfig();
if (!config) {
throw new Error('OpenID configuration not available');
}
const cacheKey = `${user.openidId}:${scopes}`;
const tokensCache = getLogStores(CacheKeys.OPENID_EXCHANGED_TOKENS);
View on GitHub (pinned to 5ff282f900)
Solutions
- Ensure the user is authenticated via the OpenID strategy before invoking any OBO-dependent path.
- Verify the openidId claim is mapped onto req.user in the OpenID strategy callback.
- Gate the calling feature on user.openidId presence and surface a friendly 'sign in with SSO' message otherwise.
Example fix
// before
const token = await getGraphApiToken(req.user, accessToken, scopes);
// after
if (!req.user?.openidId) {
return res.status(403).json({ error: 'Sign in with OpenID SSO to use this feature.' });
}
const token = await getGraphApiToken(req.user, accessToken, scopes); Defensive patterns
Strategy: validation
Validate before calling
function assertOpenIdUser(user) {
if (!user?.openidId) throw new Error('OpenID-authenticated user required for OBO');
} Type guard
const isOpenIdUser = (u) => !!u?.openidId;
Prevention
- Gate OBO-dependent features on user.openidId presence.
- Ensure the OpenID strategy maps the openidId claim onto the session user.
- Refuse non-OpenID sessions at the route boundary, not inside the token service.
When it happens
Trigger: Any caller invokes exchangeOboToken (directly or via getGraphApiToken) with a user object whose openidId is undefined/null. Typically a session that authenticated through a non-OpenID strategy (local/HEADER_AUTH) but the code path assumed OpenID.
Common situations: A feature gated on OpenID was enabled for a user who logged in via local auth. The user object was built from a JWT that lacked the openidId claim after a config change. Mixed-auth deployment where some sessions are OpenID and some are not, and the Graph/MCP path was hit by the wrong one.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Graph token acquisition failed: ${error.message}
- Failed to authenticate OAuth tool
- [MCP][${serverName}][${toolName}] upstream authentication fa
- [MCP][${serverName}][${toolName}] OAuth authentication requi
- idToken is missing
AI-assisted analysis of danny-avila/LibreChat@5ff282f900 (2026-08-12).
Data as JSON: /api/errors/92e170aa7915afe4.
Report an issue: GitHub.