LibreChat
LibreChat copied to clipboard
Enhancement: support authorization via auth proxy
What features would you like to see added?
Option FORWARD_AUTH_HEADER
More details
There are various authorizing reverse proxies. Here are two examples:
https://github.com/oauth2-proxy/oauth2-proxy
https://github.com/Jipok/Jauth
They take care of authentication, authorization, registration, etc. They are easy to support for the developer - all that is needed is to process the configured header(most often this is Remote-User or X-Forwarded-User) where the username is specified. They are also convenient for the user, since there is no need to remember/store extra login/password pairs, provide a single entry point for their own services and increase security.
I'm not good at coding in js, but I was able to implement a simple way that works for me: Just one change in: https://github.com/danny-avila/LibreChat/blob/dd8038b375901d52eb2d2db32ad43534909e90ba/api/server/controllers/AuthController.js#L72-L77
const refreshController = async (req, res) => {
const refreshToken = req.headers.cookie ? cookies.parse(req.headers.cookie).refreshToken : null;
// Handle Remote-User from auth proxy
if (!refreshToken && process.env.FORWARD_AUTH_HEADER) {
let forwardedUserName
const headerName = process.env.FORWARD_AUTH_HEADER.toLowerCase()
if (req.headers.hasOwnProperty(headerName)) {
forwardedUserName = req.headers[headerName];
} else {
return res.status(500).send('FORWARD_AUTH_HEADER('+headerName+') not provided');
}
// If user doesn't exist, register them
let user = await User.findOne({ username: forwardedUserName }, '_id').lean();
if (!user) {
//determine if this is the first registered user (not counting anonymous_user)
const isFirstRegisteredUser = (await User.countDocuments({})) === 0;
const newUser = await new User({
provider: 'local',
email: forwardedUserName + '@local.none',
password: crypto.randomBytes(Math.ceil(10)).toString('hex'),
name: forwardedUserName,
username: forwardedUserName,
avatar: null,
role: isFirstRegisteredUser ? 'ADMIN' : 'USER',
});
const salt = bcrypt.genSaltSync(10);
const hash = bcrypt.hashSync(newUser.password, salt);
newUser.password = hash;
await newUser.save();
}
user = await User.findOne({ username: forwardedUserName }, '_id').lean();
const token = await setAuthTokens(user._id, res);
return res.status(200).send({ token, user });
}
if (!refreshToken) {
return res.status(200).send('Refresh token not provided');
}
...
I understand that this code hardly corresponds to the complex architecture of the project. But it works for me and I hope someone can implement it correctly. The changes that really need to be made are resetting the jwt token if it is expired/incorrect so that the code can issue a new one automatically instead of showing the user the login page.
Documentation example: https://support.getgrist.com/install/forwarded-headers/#forwarded-headers
Which components are impacted by your request?
Endpoints
Pictures
https://github.com/danny-avila/LibreChat/assets/25588359/1fcf11f6-b302-4884-86ed-48efc3c80575
Code of Conduct
- [X] I agree to follow this project's Code of Conduct