🪪 feat: Add Google OAuth Login to Admin Panel#36
Conversation
|
@codex review |
|
Codex Review: Didn't find any major issues. More of your lovely PRs please. ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
danny-avila
left a comment
There was a problem hiding this comment.
Thanks for this, @dustinhealy — the feature itself is solid and the backend supports it (the admin Google routes exist in LibreChat: api/server/routes/admin/auth.js /oauth/google and /oauth/google/callback, backed by the googleAdmin passport strategy). Requesting changes purely on mergeability/freshness, not on the approach.
Blocking: branch is conflicting and stale
The PR currently reports mergeable: CONFLICTING / DIRTY against main, and it predates the OpenID/admin-auth refactors that have since landed:
- #46 — body-based OpenID refresh for cross-origin admin panels
- #50 — OpenID redirect origin handling + startup logs
- #59 — guard admin SSO PKCE verifier loss
Those reworked the same files this PR touches, so it needs a real rebase before it can be reviewed/merged. Expect to re-reconcile at least:
src/server/auth.ts— the OAuth init/exchange + PKCE/origin handling has changed substantially; the new Google flow should reuse the currentopenidLoginFn/oauthExchangeFnpatterns (PKCEcode_challenge, origin binding, sessioncodeVerifier) rather than the older shape this branch was built on.src/components/AuthCard.tsxandsrc/routes/login.tsx— SSO button/auto-redirect wiring has moved.src/routeTree.gen.ts— regenerate (don't hand-merge) after addingsrc/routes/auth/google/callback.tsx.src/types/auth.ts,src/types/server.ts,src/constants/oauth.ts, locales — re-check against current definitions.
Requested changes
- Rebase onto latest
mainand resolve conflicts; regeneraterouteTree.gen.tsrather than resolving it by hand. - After rebase, align the Google callback/exchange with the current admin OAuth contract (PKCE + origin binding via request headers; the backend strips
redirect_uri/code_challenge/redirectTo, seepackages/api/src/auth/exchange.ts). - Confirm
tsc --noEmitandvitest runare green post-rebase, and that the e2emock-backendupdates still reflect the current exchange shape.
Happy to re-review as soon as it's rebased and green. Thanks again!
Renders a "Continue with Google" button on the admin login page alongside the existing OpenID flow, fully delegating to LibreChat's already-wired /api/admin/oauth/google routes (no upstream changes needed). Provider availability is now sourced from LibreChat's public /api/config startup payload — the same endpoint LibreChat's own client uses — instead of the openid-only /admin/oauth/openid/check endpoint. This makes the admin panel pick up github/discord/saml automatically once they're added to the registry, with no per-provider check endpoints required. Detection layer landed as a small src/constants/oauth.ts registry plus a generalized oauthLoginFn / oauthExchangeFn pair parameterized by provider. The exchange wire body to LibreChat is unchanged; provider tagging is admin-panel-internal and drives session.tokenProvider for telemetry. Refresh-token cookie forwarding stays openid-only because LibreChat's /api/auth/refresh only branches on token_provider=openid (and only when OPENID_REUSE_TOKENS is set). Google sessions correctly fall through the default JWT refresh path. ADMIN_SSO_ONLY semantics extended for the multi-provider world: hides the password form whenever any SSO provider is enabled, auto-redirects only when exactly one provider is configured.
bc620cb to
9ab4f6a
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
When a user is sent to /login?redirect=/some/page and manually clicks an OAuth provider button, this branch navigates to the provider URL without adding the redirectTo parameter that the auto-redirect branch adds above. As a result the callback can only redirect to /, so Google SSO users who were bounced from a protected route land on the dashboard instead of the page they originally requested; build a URL here and append redirectTo before assigning window.location.href.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The inline provider button rendering in AuthCard branched on the presence of a logo glyph: branded providers needed children for the logo+text composition (and an eslint-disable for click-ui's button-requires-label rule), while unbranded providers used the label prop directly. Both paths shared identical type, disabled, onClick, and fillWidth wiring. Moving the branch into a dedicated OAuthButton scopes the eslint-disable to a single helper, collapses the provider-rendering site in AuthCard to a plain map, and gives the future github/discord/saml additions a single file to land their glyph rules in.
The Google callback's error-state retry was rendered as a bare anchor to /login, which bypasses the router's basepath. Under VITE_BASE_PATH=/admin that anchor navigates to the domain root and lands outside the mounted admin panel, where it 404s. The OpenID callback already uses TanStack Link for the same path; routing the Google callback through Link too keeps both flows aligned and basepath-correct.
The webServer readiness URL still targeted the removed /api/admin/oauth/openid/check endpoint, so Playwright spent the configured 10s timeout polling a 404 before declaring the mock backend ready. /api/config is the live provider-discovery endpoint the admin panel now reads on the SSR login boundary and is served from the same mock process, so it doubles as a real liveness signal.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bde4844072
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
When a user is sent to /login?redirect=... and manually clicks Google/OpenID, this branch assigns the returned auth URL directly, unlike the auto-redirect path above that appends redirectTo. That drops the protected-route destination for all non-auto SSO clicks, so after completing OAuth the user cannot be returned to the page that originally required auth. Mirror the auto-redirect handling before navigating.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
`getStartupConfigFn` previously surfaced any provider whose `*LoginEnabled` flag was true, even when LibreChat had not enabled social login at all. With `ALLOW_SOCIAL_LOGIN=false` (the default in LibreChat's `.env.example`) the upstream `configureSocialLogins` never registers the `googleAdmin` passport strategy, so the admin Google button auto-redirected into an "Unknown authentication strategy" 500 and `ADMIN_SSO_ONLY=true` deployments hid the password fallback behind it. The provider registry now carries an opt-in `social` flag, and `getStartupConfigFn` filters social providers on `socialLoginEnabled` while leaving openid (its own registration path) unaffected. The same call now forwards the `X-Tenant-Id` header from the BFF request to LibreChat's `/api/config`, which is mounted behind `preAuthTenantMiddleware`. Without it, multi-tenant deployments with tenant-specific `registration.socialLogins` fell back to the base config and rendered the wrong provider set for the tenant the user was actually accessing.
Adds an explicit comment near the session write in `oauthExchangeFn` documenting why non-openid OAuth admin sessions arrive without a refresh token: LibreChat's `googleAdmin` passport strategy does not request `access_type=offline`, and `createOAuthHandler` only forwards refresh tokens when `provider === 'openid' && OPENID_REUSE_TOKENS=true`. The practical effect is that Google admin users are re-prompted at JWT expiry. A proper fix lives upstream in LibreChat (capture and expose a refresh token for Google admin exchanges). Tracking that as a separate follow-up.
LibreChat's admin OAuth routes redirect passport/PKCE/auth failures back with `error` and `error_description` query params (e.g. `pkce_store_failed`, `auth_failed`). The callback loaders previously accepted only `code` and treated everything else as `invalid_code`, so a cancelled Google consent or an upstream auth failure surfaced "Authorization code has expired" instead of the real reason. Both google and openid callbacks now accept `error` / `error_description` in their search schemas and render the upstream description verbatim. Falling back to `error` itself keeps the page useful when the upstream redirect omits the description.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit f4eb8be. Configure here.
`hidePasswordForm` previously included a `providers.length > 0` clause as a defensive fallback, but combined with the new social-login gate that can legitimately leave `providers` empty for an `ADMIN_SSO_ONLY=true` deployment (e.g. only Google configured but upstream `ALLOW_SOCIAL_LOGIN=false`), it leaked the password form back into the page and defeated the deployer's SSO-only intent. `hidePasswordForm` now collapses to `ssoOnly`. When `ssoOnly` is set and discovery returns no providers, `AuthCard` shows a warning banner via a new `com_auth_sso_required_unconfigured` locale key so the misconfigured state is visible instead of silently degrading.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 115433a9c4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The Bun server's metrics wrapper normalizes any path missing from `KNOWN_APP_ROUTES` to `unknown`, so the new Google admin callback was collapsing every login attempt (success or failure) into the same bucket as bot probes and 404s. Adds `/auth/google/callback` to the registry next to the existing openid entry, and to the metrics test matrix so the mapping is locked in.
|
@codex review |
1 similar comment
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
librechat-admin-panel/src/components/AuthCard.tsx
Lines 208 to 209 in 8463b66
When an admin is sent to /login?redirect=/grants and has to choose among multiple SSO providers (for example ADMIN_SSO_ONLY=true with OpenID and Google), this manual provider path sends the raw auth URL without carrying redirectTo, while the callback routes redirect to / after exchange. The result is that successful Google/OpenID SSO from a protected page lands on the dashboard instead of the originally requested route; mirror the auto-redirect path's redirect preservation or persist the redirect through the exchange.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8463b6677a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| for (const def of OAUTH_PROVIDERS) { | ||
| if (config[def.enabledKey as keyof t.StartupConfigResponse] !== true) continue; | ||
| /** |
There was a problem hiding this comment.
Respect socialLogins when resolving providers
When a deployment leaves Google credentials set but removes google from LibreChat's registration.socialLogins for a tenant/base config, /api/config can still have googleLoginEnabled: true; this loop only checks the env-derived enabled flag, so the admin panel renders and starts a Google login that LibreChat's startup config intentionally hides. Filter against the socialLogins list from /api/config before pushing social providers.
Useful? React with 👍 / 👎.
| for (const def of OAUTH_PROVIDERS) { | ||
| if (config[def.enabledKey as keyof t.StartupConfigResponse] !== true) continue; | ||
| /** |
There was a problem hiding this comment.
Validate OpenID before advertising it
For OpenID deployments with the required env vars present but a bad issuer/discovery setup, /api/config can still report openidLoginEnabled: true; this now adds the OpenID provider without exercising LibreChat's admin OpenID check, so the login page (or SSO-only auto-redirect) sends admins into a failing OAuth flow that the previous /api/admin/oauth/openid/check probe would have hidden. Keep the admin check for OpenID or validate the provider before pushing it here.
Useful? React with 👍 / 👎.

Summary
Adds a "Continue with Google" button to the admin login page alongside the existing OpenID flow. LibreChat's
/api/admin/oauth/googleand/api/admin/oauth/google/callbackroutes were already wired, we just needed to consume them in the admin panel.The provider availability layer was rewritten while in the area: instead of the openid-only
GET /api/admin/oauth/openid/checkprobe, the loader now reads LibreChat's existing/api/configstartup payload (the same endpoint LibreChat's chat client uses) and derives a provider availability map from*LoginEnabledflags. This eliminates the need for per-provider check endpoints, and makes adding future providers (GitHub, Discord, SAML) a one-line registry append.Provider registry:
src/constants/oauth.ts— single registry entry per provider (startPath,callbackRoute,enabledKey, optionallabelKey/imageKeyfor OIDC/SAML branding overrides, optional click-uiLogoname).OAuthProviderunion widened from'openid'to'openid' | 'google'; same shape onSessionData.tokenProvider.Server functions (
src/server/auth.ts):checkOpenIdFn/openIdCheckOptionswithgetStartupConfigFn/startupConfigOptionswhich fetches/api/configonce and returns{ providers, ssoOnly }.openidLoginFnwith provider-parameterizedoauthLoginFn(PKCE generation centralized inbuildOAuthLoginUrl).oauthExchangeFnnow takes{ code, provider }; the upstream wire body to/api/admin/oauth/exchangeis unchanged (still{ code, code_verifier }) —provideris admin-panel-internal and only drives the session'stokenProviderfield.refreshAdminToken) is openid-only because LibreChat's admin OAuth exchange only exposes a refresh token for OpenID. Google admin sessions arrive without one: thegoogleAdminpassport strategy atapi/strategies/googleStrategy.jsdoes not passaccess_type=offline, andcreateOAuthHandleratapi/server/controllers/auth/oauth.js:46-50only forwards refresh tokens whenprovider === 'openid' && OPENID_REUSE_TOKENS=true. As a result, Google admin users are re-prompted at JWT expiry (~15 min default). Tracking the upstream LibreChat change to capture and expose a Google admin refresh token as a follow-up; the admin panel side is ready to consume one as soon as it ships.UI (
src/components/AuthCard.tsx,src/routes/login.tsx):Logoglyph; OIDC consumesopenidLabel/openidImageUrlfrom/api/configwhen set by the deployer.ADMIN_SSO_ONLYsemantics extended for the multi-provider world: hides the password form whenever any SSO provider is enabled; auto-redirects only when exactly one provider is configured. Per product requirement,ssoOnlywith multiple providers shows all buttons with no auto-redirect./api/configfetch on the SSR boundary — no client-visible discovery roundtrip.Routes:
src/routes/auth/google/callback.tsxmirroring the openid callback; passesprovider: 'google'tooauthExchangeFn.provider: 'openid'for symmetry.Change Type
Testing
Manual end-to-end (real Google OAuth client):
/oauth/google/callback(LibreChat) and/api/admin/oauth/google/callback(admin panel).GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET/ALLOW_SOCIAL_LOGIN=true.GET /api/configreturnsgoogleLoginEnabled: true.ALLOW_SOCIAL_REGISTRATIONonly applies to the chat path)./login→ Google consent → exchange succeeded → admin dashboard rendered,tokenProvider: 'google'set in the encrypted admin session.Automated:
e2e/login.spec.ts— added "shows Google button alongside OpenID when both are configured" assertion. Existing OpenID/SSO/2FA tests adapted to the new button labels (Continue with OpenID/Continue with Google).e2e/mock-backend.mjs— addedGET /api/confighandler returning bothopenidLoginEnabled: trueandgoogleLoginEnabled: trueso multi-provider rendering exercises in CI.e2e/auth.setup.ts— fixed pre-existingSign In/Sign incasing mismatch that was blocking the suite locally.Test Configuration
devbranch (last syncb632367c8)feat/admin-google-oauth(this branch)Checklist
Note
High Risk
Touches admin authentication (OAuth discovery, PKCE, session
tokenProvider, SSO-only policy); Google sessions may lack refresh tokens until upstream LibreChat changes.Overview
Adds Google (“Continue with Google”) on the admin login page and refactors SSO from a single OpenID probe to multi-provider flows driven by LibreChat’s public
/api/configpayload.Discovery & server:
checkOpenIdFn/openidLoginFnare replaced bygetStartupConfigFn(returns{ providers, ssoOnly }from*LoginEnabledflags, respectssocialLoginEnabledfor Google, forwardsX-Tenant-Id) andoauthLoginFn/oauthExchangeFnwith aproviderargument (openid|google). Provider metadata lives insrc/constants/oauth.ts.UI:
AuthCardrenders oneOAuthButtonper enabled provider (password form + separator when allowed).ADMIN_SSO_ONLYhides password login even if no providers resolve, with a warning when misconfigured; auto-redirect runs only when SSO-only and exactly one provider is available.Routes & tests: New
/auth/google/callback; OpenID/Google callbacks passproviderand surface upstreamerror/error_description. E2E mock servesGET /api/config; Playwright targets Sign in and OpenID/Google button labels.Reviewed by Cursor Bugbot for commit 8463b66. Bugbot is set up for automated code reviews on this repo. Configure here.