mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-16 16:30:15 +01:00
Adds federatedTokens object to user during authentication to enable
federated provider token template variables in LibreChat configuration.
Changes:
- OpenID JWT Strategy: Extract raw JWT from Authorization header and
attach as federatedTokens.access_token to enable {{LIBRECHAT_OPENID_TOKEN}}
placeholder resolution
- OpenID Strategy: Attach tokenset tokens as federatedTokens object to
standardize token access across both authentication strategies
This enables proper token propagation for custom endpoints and MCP
servers that require federated provider tokens for authorization.
Resolves missing token issue reported by @ramden in PR #9931
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Denis Ramic <denis.ramic@nfon.com>
Co-Authored-By: Claude <noreply@anthropic.com>
110 lines
3.9 KiB
JavaScript
110 lines
3.9 KiB
JavaScript
const jwksRsa = require('jwks-rsa');
|
|
const { logger } = require('@librechat/data-schemas');
|
|
const { HttpsProxyAgent } = require('https-proxy-agent');
|
|
const { SystemRoles } = require('librechat-data-provider');
|
|
const { Strategy: JwtStrategy, ExtractJwt } = require('passport-jwt');
|
|
const { isEnabled, findOpenIDUser } = require('@librechat/api');
|
|
const { updateUser, findUser } = require('~/models');
|
|
|
|
/**
|
|
* @function openIdJwtLogin
|
|
* @param {import('openid-client').Configuration} openIdConfig - Configuration object for the JWT strategy.
|
|
* @returns {JwtStrategy}
|
|
* @description This function creates a JWT strategy for OpenID authentication.
|
|
* It uses the jwks-rsa library to retrieve the signing key from a JWKS endpoint.
|
|
* The strategy extracts the JWT from the Authorization header as a Bearer token.
|
|
* The JWT is then verified using the signing key, and the user is retrieved from the database.
|
|
*
|
|
* Includes email fallback mechanism:
|
|
* 1. Primary lookup: Search user by openidId (sub claim)
|
|
* 2. Fallback lookup: If not found, search by email claim
|
|
* 3. User migration: If found by email without openidId, migrate the user by adding openidId
|
|
* 4. Provider validation: Ensures users registered with other providers cannot use OpenID
|
|
*
|
|
* This enables seamless migration for existing users when SharePoint integration is enabled.
|
|
*/
|
|
const openIdJwtLogin = (openIdConfig) => {
|
|
let jwksRsaOptions = {
|
|
cache: isEnabled(process.env.OPENID_JWKS_URL_CACHE_ENABLED) || true,
|
|
cacheMaxAge: process.env.OPENID_JWKS_URL_CACHE_TIME
|
|
? eval(process.env.OPENID_JWKS_URL_CACHE_TIME)
|
|
: 60000,
|
|
jwksUri: openIdConfig.serverMetadata().jwks_uri,
|
|
};
|
|
|
|
if (process.env.PROXY) {
|
|
jwksRsaOptions.requestAgent = new HttpsProxyAgent(process.env.PROXY);
|
|
}
|
|
|
|
return new JwtStrategy(
|
|
{
|
|
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
|
secretOrKeyProvider: jwksRsa.passportJwtSecret(jwksRsaOptions),
|
|
passReqToCallback: true, // Pass request to callback to access raw token
|
|
},
|
|
/**
|
|
* @param {Express.Request} req
|
|
* @param {import('openid-client').IDToken} payload
|
|
* @param {import('passport-jwt').VerifyCallback} done
|
|
*/
|
|
async (req, payload, done) => {
|
|
try {
|
|
// Extract the raw JWT token from the Authorization header
|
|
const authHeader = req.headers.authorization;
|
|
const rawToken = authHeader?.replace('Bearer ', '');
|
|
|
|
const { user, error, migration } = await findOpenIDUser({
|
|
findUser,
|
|
email: payload?.email,
|
|
openidId: payload?.sub,
|
|
idOnTheSource: payload?.oid,
|
|
strategyName: 'openIdJwtLogin',
|
|
});
|
|
|
|
if (error) {
|
|
done(null, false, { message: error });
|
|
return;
|
|
}
|
|
|
|
if (user) {
|
|
user.id = user._id.toString();
|
|
|
|
const updateData = {};
|
|
if (migration) {
|
|
updateData.provider = 'openid';
|
|
updateData.openidId = payload?.sub;
|
|
}
|
|
if (!user.role) {
|
|
user.role = SystemRoles.USER;
|
|
updateData.role = user.role;
|
|
}
|
|
|
|
if (Object.keys(updateData).length > 0) {
|
|
await updateUser(user.id, updateData);
|
|
}
|
|
|
|
// Add federated tokens for OIDC placeholder processing
|
|
// Use the raw JWT token as the access token
|
|
user.federatedTokens = {
|
|
access_token: rawToken,
|
|
refresh_token: payload.refresh_token,
|
|
expires_at: payload.exp,
|
|
};
|
|
|
|
done(null, user);
|
|
} else {
|
|
logger.warn(
|
|
'[openIdJwtLogin] openId JwtStrategy => no user found with the sub claims: ' +
|
|
payload?.sub +
|
|
(payload?.email ? ' or email: ' + payload.email : ''),
|
|
);
|
|
done(null, false);
|
|
}
|
|
} catch (err) {
|
|
done(err, false);
|
|
}
|
|
},
|
|
);
|
|
};
|
|
|
|
module.exports = openIdJwtLogin;
|