Skip to main content

Overview

LibreChat supports multiple authentication strategies to accommodate different use cases and security requirements:
  • JWT (JSON Web Tokens) - Primary authentication method
  • Local Authentication - Username/password with Passport.js
  • OAuth 2.0 - Social login providers (Google, GitHub, Discord, etc.)
  • OpenID Connect - Enterprise SSO integration
  • LDAP - Active Directory and LDAP servers
  • SAML 2.0 - Enterprise identity providers

Authentication Flow

Standard Login Flow

JWT Authentication

JWT Strategy Configuration

LibreChat uses Passport.js with JWT strategy:
const { Strategy: JwtStrategy, ExtractJwt } = require('passport-jwt');

const jwtLogin = () =>
  new JwtStrategy(
    {
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKey: process.env.JWT_SECRET,
    },
    async (payload, done) => {
      try {
        const user = await getUserById(payload?.id, '-password -__v -totpSecret -backupCodes');
        if (user) {
          user.id = user._id.toString();
          if (!user.role) {
            user.role = SystemRoles.USER;
            await updateUser(user.id, { role: user.role });
          }
          done(null, user);
        } else {
          done(null, false);
        }
      } catch (err) {
        done(err, false);
      }
    },
  );

Using JWT Authentication

Include the JWT token in the Authorization header:
curl -X GET https://api.example.com/api/user \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
const response = await fetch('http://localhost:3080/api/user', {
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json'
  }
});

JWT Middleware

Protected routes use the requireJwtAuth middleware:
const requireJwtAuth = (req, res, next) => {
  const cookieHeader = req.headers.cookie;
  const tokenProvider = cookieHeader ? cookies.parse(cookieHeader).token_provider : null;

  // Switch to OpenID JWT if using OpenID tokens
  if (tokenProvider === 'openid' && isEnabled(process.env.OPENID_REUSE_TOKENS)) {
    return passport.authenticate('openidJwt', { session: false })(req, res, next);
  }

  return passport.authenticate('jwt', { session: false })(req, res, next);
};

Token Expiry

Configure token lifetimes via environment variables:
SESSION_EXPIRY=900000          # JWT token: 15 minutes (in milliseconds)
REFRESH_TOKEN_EXPIRY=604800000 # Refresh token: 7 days (in milliseconds)
Access tokens (JWT) are short-lived for security. Use refresh tokens to obtain new access tokens without re-authentication.

Local Authentication

Login Endpoint

router.post(
  '/login',
  middleware.logHeaders,
  middleware.loginLimiter,
  middleware.checkBan,
  ldapAuth ? middleware.requireLdapAuth : middleware.requireLocalAuth,
  setBalanceConfig,
  loginController,
);

Login Request

curl -X POST http://localhost:3080/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "password": "your-password"
  }'

Login Response

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "user": {
    "id": "507f1f77bcf86cd799439011",
    "email": "[email protected]",
    "name": "John Doe",
    "username": "johndoe",
    "role": "USER",
    "provider": "local",
    "avatar": null,
    "emailVerified": true
  }
}
The response also sets HTTP-only cookies:
Set-Cookie: refreshToken=...; HttpOnly; Secure; SameSite=Strict
Set-Cookie: token_provider=librechat; HttpOnly; Secure; SameSite=Strict

Local Strategy Implementation

The local strategy validates username and password:
module.exports = () =>
  new PassportLocalStrategy(
    {
      usernameField: 'email',
      passwordField: 'password',
      session: false,
      passReqToCallback: true,
    },
    async (req, email, password, done) => {
      try {
        // Find user
        const user = await findUser({ email: email.trim() }, '+password');
        if (!user) {
          return done(null, false, { message: 'Email does not exist.' });
        }

        // Verify password
        const isMatch = await comparePassword(user, password);
        if (!isMatch) {
          return done(null, false, { message: 'Incorrect password.' });
        }

        // Check email verification
        if (!user.emailVerified && !unverifiedAllowed) {
          return done(null, user, { message: 'Email not verified.' });
        }

        return done(null, user);
      } catch (err) {
        return done(err);
      }
    },
  );

Token Refresh

Refresh Token Flow

When the access token expires, use the refresh token to obtain a new one:
const refreshController = async (req, res) => {
  const parsedCookies = req.headers.cookie ? cookies.parse(req.headers.cookie) : {};
  const refreshToken = parsedCookies.refreshToken;

  if (!refreshToken) {
    return res.status(200).send('Refresh token not provided');
  }

  try {
    // Verify refresh token
    const payload = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET);
    const user = await getUserById(payload.id, '-password -__v -totpSecret -backupCodes');
    
    if (!user) {
      return res.status(401).redirect('/login');
    }

    // Find session
    const session = await findSession({
      userId: payload.id,
      refreshToken: refreshToken,
    });

    if (session && session.expiration > new Date()) {
      // Generate new access token
      const token = await setAuthTokens(payload.id, res, session);
      res.status(200).send({ token, user });
    } else {
      res.status(401).send('Refresh token expired or not found');
    }
  } catch (err) {
    res.status(403).send('Invalid refresh token');
  }
};

Refresh Request Example

curl -X POST http://localhost:3080/api/auth/refresh \
  -H "Cookie: refreshToken=your-refresh-token"

OAuth 2.0 Authentication

LibreChat supports multiple OAuth providers:

Supported Providers

  • Google OAuth 2.0
  • GitHub OAuth
  • Discord OAuth
  • Facebook Login
  • Apple Sign In

OAuth Configuration

Enable social login:
ALLOW_SOCIAL_LOGIN=true

OAuth Flow

// Google OAuth
router.get(
  '/google',
  passport.authenticate('google', {
    scope: ['openid', 'profile', 'email'],
    session: false,
  }),
);

router.get(
  '/google/callback',
  passport.authenticate('google', {
    failureRedirect: `${domains.client}/oauth/error`,
    failureMessage: true,
    session: false,
    scope: ['openid', 'profile', 'email'],
  }),
  setBalanceConfig,
  checkDomainAllowed,
  oauthHandler,
);

OAuth Login Flow

  1. Initiate OAuth - Redirect user to provider:
    GET /oauth/google
    
  2. User Authorizes - User grants permissions on provider site
  3. Callback - Provider redirects to callback URL:
    GET /oauth/google/callback?code=...
    
  4. Token Exchange - Server exchanges code for tokens
  5. User Creation/Login - Find or create user account
  6. Session Creation - Generate JWT and refresh tokens
OAuth tokens are handled server-side. The client receives standard LibreChat JWT tokens.

OpenID Connect

OpenID Configuration

For enterprise SSO with OpenID Connect:
# OpenID Provider Configuration
OPENID_ISSUER=https://accounts.google.com
OPENID_CLIENT_ID=your-client-id
OPENID_CLIENT_SECRET=your-client-secret
OPENID_CALLBACK_URL=https://your-domain.com/oauth/openid/callback
OPENID_SCOPE="openid profile email"

# Token Management
OPENID_REUSE_TOKENS=true

OpenID Token Flow

const setOpenIDAuthTokens = (tokenset, req, res, userId, existingRefreshToken) => {
  const refreshToken = tokenset.refresh_token || existingRefreshToken;
  const appAuthToken = tokenset.id_token || tokenset.access_token;

  // Store tokens in session (avoids large cookies)
  if (req.session) {
    req.session.openidTokens = {
      accessToken: tokenset.access_token,
      idToken: tokenset.id_token,
      refreshToken: refreshToken,
      expiresAt: expirationDate.getTime(),
    };
  }

  // Set cookies
  res.cookie('refreshToken', refreshToken, {
    expires: expirationDate,
    httpOnly: true,
    secure: shouldUseSecureCookie(),
    sameSite: 'strict',
  });

  res.cookie('token_provider', 'openid', {
    expires: expirationDate,
    httpOnly: true,
    secure: shouldUseSecureCookie(),
    sameSite: 'strict',
  });

  return appAuthToken;
};
OpenID tokens are stored server-side in express-session to avoid HTTP/2 header size limits for users with many group memberships.

LDAP Authentication

LDAP Configuration

Enable LDAP authentication:
LDAP_URL=ldap://localhost:389
LDAP_USER_SEARCH_BASE=ou=users,dc=example,dc=com
LDAP_BIND_DN=cn=admin,dc=example,dc=com
LDAP_BIND_CREDENTIALS=admin-password
LDAP_USER_SEARCH_FILTER=(mail={{username}})

LDAP Strategy Setup

// Initialize LDAP if configured
if (process.env.LDAP_URL && process.env.LDAP_USER_SEARCH_BASE) {
  passport.use(ldapLogin);
}

// Use LDAP auth for login
const ldapAuth = !!process.env.LDAP_URL && !!process.env.LDAP_USER_SEARCH_BASE;
router.post(
  '/login',
  ldapAuth ? middleware.requireLdapAuth : middleware.requireLocalAuth,
  loginController,
);

Two-Factor Authentication (2FA)

2FA Endpoints

// Enable 2FA
router.get('/2fa/enable', middleware.requireJwtAuth, enable2FA);

// Verify TOTP code
router.post('/2fa/verify', middleware.requireJwtAuth, verify2FA);

// Verify with temporary token
router.post('/2fa/verify-temp', middleware.checkBan, verify2FAWithTempToken);

// Confirm 2FA setup
router.post('/2fa/confirm', middleware.requireJwtAuth, confirm2FA);

// Disable 2FA
router.post('/2fa/disable', middleware.requireJwtAuth, disable2FA);

// Regenerate backup codes
router.post('/2fa/backup/regenerate', middleware.requireJwtAuth, regenerateBackupCodes);

Enable 2FA Flow

  1. Request Setup
    GET /api/auth/2fa/enable
    Authorization: Bearer {token}
    
  2. Receive QR Code
    {
      "secret": "JBSWY3DPEHPK3PXP",
      "qrCode": "data:image/png;base64,...",
      "backupCodes": ["12345678", "87654321", ...]
    }
    
  3. Verify Code
    POST /api/auth/2fa/verify
    Authorization: Bearer {token}
    Content-Type: application/json
    
    {
      "code": "123456"
    }
    
  4. Confirm 2FA
    POST /api/auth/2fa/confirm
    Authorization: Bearer {token}
    

Login with 2FA

When 2FA is enabled, the login flow changes:
  1. Initial Login - Returns temporary token
  2. Verify 2FA Code - Submit TOTP code or backup code
  3. Receive JWT - Get full access token

User Registration

Registration Endpoint

router.post(
  '/register',
  middleware.registerLimiter,
  middleware.checkBan,
  middleware.checkInviteUser,
  middleware.validateRegistration,
  registrationController,
);

Registration Request

curl -X POST http://localhost:3080/api/auth/register \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "password": "SecurePass123!",
    "name": "John Doe",
    "username": "johndoe"
  }'

Email Verification

const sendVerificationEmail = async (user) => {
  const [verifyToken, hash] = createTokenHash();

  const verificationLink = `${domains.client}/verify?token=${verifyToken}&email=${encodeURIComponent(user.email)}`;
  
  await sendEmail({
    email: user.email,
    subject: 'Verify your email',
    payload: {
      appName: process.env.APP_TITLE || 'LibreChat',
      name: user.name || user.username || user.email,
      verificationLink: verificationLink,
      year: new Date().getFullYear(),
    },
    template: 'verifyEmail.handlebars',
  });

  await createToken({
    userId: user._id,
    email: user.email,
    token: hash,
    createdAt: Date.now(),
    expiresIn: 900, // 15 minutes
  });
};
By default, users must verify their email before logging in. Set ALLOW_UNVERIFIED_EMAIL_LOGIN=true to bypass verification.

Password Reset

Request Password Reset

POST /api/auth/requestPasswordReset
Content-Type: application/json

{
  "email": "[email protected]"
}

Reset Password

POST /api/auth/resetPassword
Content-Type: application/json

{
  "userId": "507f1f77bcf86cd799439011",
  "token": "reset-token-from-email",
  "password": "NewSecurePassword123!"
}

Logout

Logout Endpoint

router.post('/logout', middleware.requireJwtAuth, logoutController);

Logout Request

curl -X POST http://localhost:3080/api/auth/logout \
  -H "Authorization: Bearer {token}" \
  -H "Cookie: refreshToken={refresh-token}"
Logout performs the following:
  1. Invalidates the refresh token session
  2. Destroys the express session
  3. Returns success message
const logoutUser = async (req, refreshToken) => {
  try {
    const userId = req.user._id;
    const session = await findSession({ userId, refreshToken });

    if (session) {
      await deleteSession({ sessionId: session._id });
    }

    req.session.destroy();
    return { status: 200, message: 'Logout successful' };
  } catch (err) {
    return { status: 500, message: err.message };
  }
};

Session Management

Session Configuration

SESSION_EXPIRY=900000          # 15 minutes
REFRESH_TOKEN_EXPIRY=604800000 # 7 days

Session Storage

Sessions are stored in MongoDB with automatic expiration:
const createSession = async (userId, { expiresIn }) => {
  const session = await Session.create({
    userId,
    refreshToken: hashedToken,
    expiration: new Date(Date.now() + expiresIn)
  });
  
  return { session, refreshToken };
};

Security Best Practices

Environment Variables

Never commit secrets to version control. Use environment variables for all sensitive data.
# Strong random secrets (minimum 32 characters)
JWT_SECRET=your-strong-random-secret-here
JWT_REFRESH_SECRET=your-refresh-secret-here

# Use secure cookies in production
SECURE_COOKIE=true

Token Security

  1. Short-lived Access Tokens - 15 minutes by default
  2. HTTP-Only Cookies - Prevent XSS attacks
  3. Secure Flag - HTTPS only in production
  4. SameSite Strict - CSRF protection
  5. Token Rotation - New refresh token on each refresh

Password Security

// Passwords are hashed with bcrypt (10 rounds)
const salt = bcrypt.genSaltSync(10);
const hashedPassword = bcrypt.hashSync(password, salt);

Rate Limiting

Authentication endpoints are rate-limited:
  • Login: Limited to prevent brute force
  • Registration: Prevent spam accounts
  • Password Reset: Prevent abuse
Rate limit thresholds are configurable in the middleware/limiters module.

Next Steps

Build docs developers (and LLMs) love