Skip to content

🔐 feat: OIDC Bearer Token Authentication for Remote Agent API#12450

Merged
danny-avila merged 32 commits intodanny-avila:devfrom
SpectralOne:feature/remote-agent-auth
May 4, 2026
Merged

🔐 feat: OIDC Bearer Token Authentication for Remote Agent API#12450
danny-avila merged 32 commits intodanny-avila:devfrom
SpectralOne:feature/remote-agent-auth

Conversation

@SpectralOne
Copy link
Copy Markdown
Contributor

@SpectralOne SpectralOne commented Mar 28, 2026

Summary

Adds OIDC Bearer token authentication support for the Remote Agent API (endpoints.agents.remoteApi). Previously only API key auth was supported. The new middleware validates Bearer tokens against a configured OIDC issuer via JWKS, with a fallback to API key auth when enabled.

Closes #11733, closes #11640

How it works

  1. Reads endpoints.agents.remoteApi.auth from AppConfig
  2. If oidc.enabled: true attempts to verify the Bearer token via JWKS
  3. JWKS URI is resolved in priority order:
    • Explicit jwksUri in librechat.yaml
    • OPENID_JWKS_URL env var
    • Auto-discovery via {issuer}/.well-known/openid-configuration
  4. On success resolves the LibreChat user via findOpenIDUser and attaches it to req.user
  5. On any failure falls back to apiKeyMiddleware if apiKey.enabled !== false, otherwise returns 401

Config example

endpoints:
  agents:
    remoteApi:
      auth:
        apiKey:
          enabled: false
        oidc:
          enabled: true
          issuer: https://auth.example.com/realms/myrealm
          # jwksUri is optional — resolved via discovery if omitted
          audience: my-client-id

Change Type

  • New feature (non-breaking change which adds functionality)
  • This change requires a documentation update

Testing

Tested interaction flow through /agents/v1 endpoint with OIDC Keycloak instance.

Also unit tests are written.

Checklist

@SpectralOne
Copy link
Copy Markdown
Contributor Author

@peeeteeer @zelark

Hello, fellas! Looking forward for your feedback, is this expected behavior or something is missing.

@peeeteeer
Copy link
Copy Markdown
Collaborator

@peeeteeer @zelark

Hello, fellas! Looking forward for your feedback, is this expected behavior or something is missing.

@SpectralOne - cool - thank you for your effort... looks like exactly what I was asking for... I did not test it so far but looks good for me - maybe one little thing...

This middleware reuses findOpenIDUser(...), but it ignores the returned migration flag and never persists the OpenID binding.

In the existing OpenID auth flow, when a user is found only by email, LibreChat treats that as a migration/linking case and updates the user record with the OpenID identity (provider: 'openid', openidId: payload.sub, etc.). Here, the request is allowed through, but the binding is never saved.

That means remote-agent auth can keep relying on email fallback on every request instead of completing the one-time identity link. This is weaker than the existing OpenID login path and can leave account linking permanently incomplete.

I think this middleware should handle migration the same way as the existing OpenID strategy, including persisting the user update before continuing.

@peeeteeer
Copy link
Copy Markdown
Collaborator

@danny-avila - would be cool to have this soon... this would enable 2 use cases for us which quite some people have been asking for... thanks :)

@SpectralOne
Copy link
Copy Markdown
Contributor Author

This middleware reuses findOpenIDUser(...), but it ignores the returned migration flag and never persists the OpenID binding.

fair point, i will fix it shortly. thanks!

@peeeteeer
Copy link
Copy Markdown
Collaborator

@SpectralOne - one more point that just came up for me... I know there is also an admin api... maybe there will be more apis in future... so, would it make sense to support optional required scopes/app roles here as part of config, so deployments can distinguish token intent at the IdP level as well?

@SpectralOne SpectralOne force-pushed the feature/remote-agent-auth branch 2 times, most recently from 82f1ec5 to 5232ee5 Compare March 30, 2026 17:46
@SpectralOne
Copy link
Copy Markdown
Contributor Author

@peeeteeer I think this is good a proposal. But i think this should be implemented in a separate Pull Request.

Config may looks like:

endpoints:
  agents:
    remoteApi:
      auth:
        oidc:
          enabled: true
          issuer: https://auth.example.com/realms/myrealm
          requiredScopes:
            - remote_agent
          requiredRoles:
            - librechat-agent
          requiredAppRoles:
            - LibreChat.RemoteAgent

or split it like this:

# remote agent API
remoteApi:
  auth:
    oidc:
      requiredRoles: [librechat-agent]

# future admin API
adminApi:
  auth:
    oidc:
      requiredRoles: [librechat-admin]

In any case, this functionality needs to be thoroughly considered.

@peeeteeer
Copy link
Copy Markdown
Collaborator

@peeeteeer I think this is good a proposal. But i think this should be implemented in a separate Pull Request.

Config may looks like:

endpoints:
  agents:
    remoteApi:
      auth:
        oidc:
          enabled: true
          issuer: https://auth.example.com/realms/myrealm
          requiredScopes:
            - remote_agent
          requiredRoles:
            - librechat-agent
          requiredAppRoles:
            - LibreChat.RemoteAgent

or split it like this:

# remote agent API
remoteApi:
  auth:
    oidc:
      requiredRoles: [librechat-agent]

# future admin API
adminApi:
  auth:
    oidc:
      requiredRoles: [librechat-admin]

In any case, this functionality needs to be thoroughly considered.

no, I meant something way simpler... when an oidc token is issued it usually contains...

{
  "header": {
 ....
  },
  "payload": {
    "iss": "who issued the token",
    "aud": "which server is the token for",
    "azp": "who (which server) requested the token",
    "scp": "scopes granted",
    "roles": ["e.g user, admin"],
    "oid": "Object ID of the user in the directory",
    "preferred_username": "the username",
    "name": "the name",
    "iat": Issued at (when token was created),
    "nbf": Not before (valid starting time),
    "exp": Expiration time (when token becomes invalid)
  }
}

typically you would have

aud: the librechat server as a client id
scp: which part of the api you have access to (agent, admin, ....)
roles: what roles do you have granted in this scope

I think we do not need the roles here since librechat has it's own authorization system.

But I do think we need to introduce/check the scope - otherwise you would be in future be able to use the token issued to the server for all operations...

so my suggestion is to add this to the config you already have....

endpoints:
  agents:
    remoteApi:
      auth:
        apiKey:
          enabled: false
        oidc:
          enabled: true
          issuer: https://auth.example.com/realms/myrealm
          # jwksUri is optional — resolved via discovery if omitted
          audience: my-client-id (this is actually the id of the librechat server)
          scope: scope to check for

in code this is easy to verify - just between the two lines here...

        const payload = await verifyOidcBearer(token, authConfig.oidc);
        
        => check for required scopes here...

        const user = await resolveUser(token, payload, findUser, updateUser);

@SpectralOne
Copy link
Copy Markdown
Contributor Author

no, I meant something way simpler... when an oidc token is issued it usually contains...

typically you would have

aud: the librechat server as a client id scp: which part of the api you have access to (agent, admin, ....) roles: what roles do you have granted in this scope

I think we do not need the roles here since librechat has it's own authorization system.

But I do think we need to introduce/check the scope - otherwise you would be in future be able to use the token issued to the server for all operations...

so my suggestion is to add this to the config you already have....

Oh, okay. Done. Indeed way simplier.

@SpectralOne
Copy link
Copy Markdown
Contributor Author

SpectralOne commented Apr 2, 2026

@danny-avila Hello, could you take a look at this PR, please?

Not urgent, when you will have free time.

@peeeteeer
Copy link
Copy Markdown
Collaborator

@SpectralOne @danny-avila - I was just testing this successfully in our environment - works without any issues... use case was integration of an Libechat agent in another website/portal...

@peeeteeer
Copy link
Copy Markdown
Collaborator

maybe one idea... when the api key auth is disabled we also should disable the generation of api keys in the UI

@danny-avila
Copy link
Copy Markdown
Owner

thanks @peeeteeer I'll be reviewing this shortly

@danny-avila
Copy link
Copy Markdown
Owner

@codex review

@danny-avila
Copy link
Copy Markdown
Owner

/gitnexus index

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7b36e24079

ℹ️ 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".

Comment thread packages/api/src/middleware/remoteAgentAuth.ts Outdated
Comment thread packages/api/src/middleware/remoteAgentAuth.ts Outdated
@SpectralOne
Copy link
Copy Markdown
Contributor Author

@danny-avila Hi, I've fixed codex review errors

@peeeteeer
Copy link
Copy Markdown
Collaborator

@danny-avila @SpectralOne - today I was coming around a shortcoming of the current implementation...

The current remote OIDC implementation only works for users that already exist in LibreChat. A valid Entra/OIDC Bearer token is verified by remoteAgentAuth, but if no matching LibreChat user is found, the request is rejected with 401 Unauthorized. The middleware does not auto-provision a LibreChat user from a valid token. Agent authorization is then decided by LibreChat’s existing ACL/group permission system, not directly by Entra token claims. That means the user must already be known locally before the existing agent-level access checks can run. In addition, the remote-agent OIDC path does not currently synchronize Entra group memberships into LibreChat group membership state. Group sync exists in the browser OpenID login flow, but not in remoteAgentAuth. As a result, remote OIDC access currently depends on pre-existing LibreChat user records and pre-existing synced group membership data.

To support a real integration into other portals if would be required to support first-time remote OIDC users by provisioning or resolving the LibreChat user record before ACL evaluation and ensure the remote OIDC flow also refreshes or otherwise guarantees the LibreChat-side group membership state used for agent access decisions.

would be cool to have it... maybe guarded by another config flag...

@SpectralOne
Copy link
Copy Markdown
Contributor Author

The current remote OIDC implementation only works for users that already exist in LibreChat

We're facing the same issue. This can be implemented the same way openidStrategy does (reuse the processOpenIDAuth function). We plan to implement that in our fork for our needs.

It would be great to have it by default, but I'm not sure where's the right place for it in LibreChat architecture.

@danny-avila
Copy link
Copy Markdown
Owner

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: eea9e9aff1

ℹ️ 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".

Comment thread packages/api/src/middleware/remoteAgentAuth.ts Outdated
Comment thread packages/api/src/middleware/remoteAgentAuth.ts
@SpectralOne
Copy link
Copy Markdown
Contributor Author

@danny-avila Hi, I've applied codex suggestions

@peeeteeer
Copy link
Copy Markdown
Collaborator

@danny-avila - I was today investigating the new admin ui and the new possibility to define roles for users... as a consequence I was creating a new enhancement request (#12769) - it would be great (required ;) ) that this then works here as well :)

@danny-avila
Copy link
Copy Markdown
Owner

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4beef5a937

ℹ️ 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".

Comment thread packages/data-provider/src/config.ts Outdated
Comment thread packages/api/src/middleware/remoteAgentAuth.ts Outdated
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e5846f5fc1

ℹ️ 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".

Comment thread packages/api/src/auth/openid.ts Outdated
@peeeteeer
Copy link
Copy Markdown
Collaborator

@danny-avila - still works doing a quick check... any thoughts for #12450 (comment) and #12450 (comment) ? Would love to see both things as well - thank you!

@danny-avila
Copy link
Copy Markdown
Owner

@codex review

@danny-avila
Copy link
Copy Markdown
Owner

@danny-avila - still works doing a quick check... any thoughts for #12450 (comment) and #12450 (comment) ? Would love to see both things as well - thank you!

I think those are important and will definitely add to roadmap

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2598f0ec94

ℹ️ 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".

Comment thread packages/api/src/middleware/remoteAgentAuth.ts
@danny-avila
Copy link
Copy Markdown
Owner

@codex review

@danny-avila
Copy link
Copy Markdown
Owner

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bfb935073b

ℹ️ 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".

Comment thread packages/api/src/middleware/remoteAgentAuth.ts Outdated
@SpectralOne
Copy link
Copy Markdown
Contributor Author

@danny-avila

Thanks for the thorough work on this, everything looks great! No conflicts with our setup, working well.

@danny-avila
Copy link
Copy Markdown
Owner

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a8547955c7

ℹ️ 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".

Comment thread packages/api/src/middleware/remoteAgentAuth.ts Outdated
@danny-avila
Copy link
Copy Markdown
Owner

@codex review

1 similar comment
@danny-avila
Copy link
Copy Markdown
Owner

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 340d0a36f6

ℹ️ 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".

Comment thread packages/api/src/middleware/remoteAgentAuth.ts Outdated
@danny-avila
Copy link
Copy Markdown
Owner

@codex review

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Bravo.

ℹ️ 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".

@danny-avila
Copy link
Copy Markdown
Owner

@codex review

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Nice work!

ℹ️ 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".

@danny-avila danny-avila merged commit 5683706 into danny-avila:dev May 4, 2026
16 checks passed
danny-avila added a commit that referenced this pull request May 5, 2026
`IUser` extends mongoose `Document`, which types `id?: any` (the optional
virtual). At runtime `id` is always `_id.toString()` for a hydrated doc,
so narrow the type to a required string.

Closes two `@rollup/plugin-typescript` TS2322 warnings introduced by
PR #12450 (OIDC Bearer Token Authentication for Remote Agent API)
where `req.user = userResolution.user` and the
`(req: Request, res: Response, next: NextFunction)` signature both
failed against the project's local `Express.User` augmentation
(`{ [key: string]: any; id: string; }`) because `IUser.id` was
`any`/optional. Narrowing here fixes both at the source rather than
casting at every assignment site.
danny-avila added a commit that referenced this pull request May 5, 2026
* 🐛 fix: Propagate User Identity to Subagent MCP Tool Calls

The `@librechat/agents` SDK's `SubagentExecutor` invokes the child
workflow with a fresh configurable of `{ thread_id }` only — the
parent's `user` / `user_id` are dropped on the way into the child
graph. The child's `ToolNode` then dispatches `ON_TOOL_EXECUTE` to the
parent's handler, which merges `{ ...configurable, ...toolConfigurable }`,
but neither side carries user identity for subagents.

Downstream MCP tools read `config.configurable.user?.id || user_id` and
got `undefined`, so `MCPManager.getConnection` fell through to the
"No connection found for server X" error path — it can't reach the
user-connection lookup without a userId.

Re-inject `user` (via `createSafeUser`) and `user_id` from `req.user`
into the configurable returned by `loadToolsForExecution`. This is the
single point all controllers (chat, Responses API, OpenAI-compat) flow
through. For the parent agent it's a no-op (outer config already
carries the same values); for subagents it fills the gap so MCP
connection lookup, user-placeholder substitution, and tools that read
configurable.user all work correctly.

* 🐛 fix: Preserve `api-user` Fallback When Injecting Subagent Identity

Codex review pointed out that the prior commit unconditionally wrote
`user_id: req.user?.id` (and `user`) into `toolConfigurable`. The handler
merges via `{ ...configurable, ...toolConfigurable }` — `toolConfigurable`
wins — so when `req.user` is absent, this overwrote the outer config's
`'api-user'` fallback (set by `responses.js` / `openai.js` for the
unauthenticated API-key path) with `undefined`, breaking MCP connection
lookup for that path.

Only inject the keys when `req.user.id` is truthy. Omitting them lets
the merge preserve whatever the outer configurable already had. Tests
updated to assert key omission for `req.user` undefined / null / present
without `id`.

* 🩹 fix: Narrow `IUser.id` to required string

`IUser` extends mongoose `Document`, which types `id?: any` (the optional
virtual). At runtime `id` is always `_id.toString()` for a hydrated doc,
so narrow the type to a required string.

Closes two `@rollup/plugin-typescript` TS2322 warnings introduced by
PR #12450 (OIDC Bearer Token Authentication for Remote Agent API)
where `req.user = userResolution.user` and the
`(req: Request, res: Response, next: NextFunction)` signature both
failed against the project's local `Express.User` augmentation
(`{ [key: string]: any; id: string; }`) because `IUser.id` was
`any`/optional. Narrowing here fixes both at the source rather than
casting at every assignment site.

* 🩹 fix: Resolve TS Build Warnings Surfaced by `IUser.id` Narrowing

Three rollup TS plugin warnings surfaced after narrowing
`IUser.id` from `any` to `string`:

- `utils/env.ts:95` — `safeUser[field] = user[field]` failed strict
  checking because indexed write through a union-typed key collapses
  the LHS to the intersection of all field write types (i.e.,
  `undefined` when fields have mixed types). The previous `id?: any`
  on IUser had been masking this. Switch to `Object.assign(safeUser,
  { [field]: user[field] })` which widens the assignment.

- `endpoints/google/initialize.ts:35` — `getUserKey({ userId:
  req.user?.id, ... })` failed because `req.user?.id` is now
  `string | undefined` (no longer `any`). Match the pattern already
  used in `endpoints/openAI/initialize.ts:49`: `req.user?.id ?? ''`.

- `middleware/remoteAgentAuth.ts:465` — pre-existing, unrelated to
  the IUser change. The local (gitignored) `express.d.ts` augments
  `express.Request` but not `express-serve-static-core.Request`,
  so the explicit `(req: Request, ...)` annotation imported from
  `'express'` resolves to a Request whose `req.user` differs from
  the one `RequestHandler` expects internally. Type the closure as
  `RequestHandler` directly so TS infers params from the augmented
  type.

* 🩹 fix: Cast `RemoteAgentAuth` Closure to `RequestHandler`

My previous attempt removed the explicit `req: Request` annotation on
the closure to side-step the outer `RequestHandler` mismatch. That
shifted the error to every helper call site inside the closure
(`getConfigOptions(req)`, `runApiKeyAuth(req, ...)` at 467/474/493/
512/531), because the helpers annotate their params with
`express.Request` (which has the local `Request.user` augmentation),
while the unannotated closure inferred `req` as
`express-serve-static-core.Request` (no augmentation). Reproduced
locally by stubbing the gitignored `src/types/express.d.ts`.

Right approach: keep the explicit `req: Request` annotation so the
closure body matches the helpers' types, then cast at the return —
`RequestHandler`'s internal `Request` resolves through
`express-serve-static-core` and lacks the augmentation, so the cast
is the boundary that bridges the two views of `req.user`.

Verified against a build with the local express.d.ts stub: zero
warnings on `remoteAgentAuth.ts`, `env.ts`, and `google/initialize.ts`.
fuuuzzy pushed a commit to fuuuzzy/LibreChat that referenced this pull request May 8, 2026
…avila#12450)

* Remote Agent Auth middleware

* consider migration and update user

* fix eslint errors

* add scope validation

* fix codex review errors

* add filter for use: sig

* add jwks-rsa deps

* Fix remote agent OIDC auth review findings

* Polish remote agent OIDC timeout coverage

* Reject remote OIDC tokens without subject

* Use tenant context for remote agent auth config

* Harden remote agent OIDC scope handling

* Polish remote agent OIDC cache and scope tests

* Resolve remote agent auth review comments

* Reuse OpenID email claim resolver for remote auth

* Skip empty OpenID email fallback claims

* Use pre-auth tenant context for remote auth config

* Downgrade expected OIDC fallback logging

* Require secure remote OIDC endpoints

* Polish remote agent auth edge cases

* Enforce unique balance records

* Bind remote OpenID users to issuer

* Fix issuer-scoped OpenID indexes

* Avoid unique balance index requirement

* Fix remote OpenID issuer normalization boundaries

* Require issuer-bound OpenID lookups

* Enforce tenant API key policy after auth

* Fix remote auth tenant policy types

* Normalize remote OIDC discovery issuer

* Allow normalized remote OIDC issuer validation

* Enforce resolved tenant OIDC policy

* Polish OpenID issuer and scope validation

---------

Co-authored-by: Danny Avila <danny@librechat.ai>
fuuuzzy pushed a commit to fuuuzzy/LibreChat that referenced this pull request May 8, 2026
…a#12950)

* 🐛 fix: Propagate User Identity to Subagent MCP Tool Calls

The `@librechat/agents` SDK's `SubagentExecutor` invokes the child
workflow with a fresh configurable of `{ thread_id }` only — the
parent's `user` / `user_id` are dropped on the way into the child
graph. The child's `ToolNode` then dispatches `ON_TOOL_EXECUTE` to the
parent's handler, which merges `{ ...configurable, ...toolConfigurable }`,
but neither side carries user identity for subagents.

Downstream MCP tools read `config.configurable.user?.id || user_id` and
got `undefined`, so `MCPManager.getConnection` fell through to the
"No connection found for server X" error path — it can't reach the
user-connection lookup without a userId.

Re-inject `user` (via `createSafeUser`) and `user_id` from `req.user`
into the configurable returned by `loadToolsForExecution`. This is the
single point all controllers (chat, Responses API, OpenAI-compat) flow
through. For the parent agent it's a no-op (outer config already
carries the same values); for subagents it fills the gap so MCP
connection lookup, user-placeholder substitution, and tools that read
configurable.user all work correctly.

* 🐛 fix: Preserve `api-user` Fallback When Injecting Subagent Identity

Codex review pointed out that the prior commit unconditionally wrote
`user_id: req.user?.id` (and `user`) into `toolConfigurable`. The handler
merges via `{ ...configurable, ...toolConfigurable }` — `toolConfigurable`
wins — so when `req.user` is absent, this overwrote the outer config's
`'api-user'` fallback (set by `responses.js` / `openai.js` for the
unauthenticated API-key path) with `undefined`, breaking MCP connection
lookup for that path.

Only inject the keys when `req.user.id` is truthy. Omitting them lets
the merge preserve whatever the outer configurable already had. Tests
updated to assert key omission for `req.user` undefined / null / present
without `id`.

* 🩹 fix: Narrow `IUser.id` to required string

`IUser` extends mongoose `Document`, which types `id?: any` (the optional
virtual). At runtime `id` is always `_id.toString()` for a hydrated doc,
so narrow the type to a required string.

Closes two `@rollup/plugin-typescript` TS2322 warnings introduced by
PR danny-avila#12450 (OIDC Bearer Token Authentication for Remote Agent API)
where `req.user = userResolution.user` and the
`(req: Request, res: Response, next: NextFunction)` signature both
failed against the project's local `Express.User` augmentation
(`{ [key: string]: any; id: string; }`) because `IUser.id` was
`any`/optional. Narrowing here fixes both at the source rather than
casting at every assignment site.

* 🩹 fix: Resolve TS Build Warnings Surfaced by `IUser.id` Narrowing

Three rollup TS plugin warnings surfaced after narrowing
`IUser.id` from `any` to `string`:

- `utils/env.ts:95` — `safeUser[field] = user[field]` failed strict
  checking because indexed write through a union-typed key collapses
  the LHS to the intersection of all field write types (i.e.,
  `undefined` when fields have mixed types). The previous `id?: any`
  on IUser had been masking this. Switch to `Object.assign(safeUser,
  { [field]: user[field] })` which widens the assignment.

- `endpoints/google/initialize.ts:35` — `getUserKey({ userId:
  req.user?.id, ... })` failed because `req.user?.id` is now
  `string | undefined` (no longer `any`). Match the pattern already
  used in `endpoints/openAI/initialize.ts:49`: `req.user?.id ?? ''`.

- `middleware/remoteAgentAuth.ts:465` — pre-existing, unrelated to
  the IUser change. The local (gitignored) `express.d.ts` augments
  `express.Request` but not `express-serve-static-core.Request`,
  so the explicit `(req: Request, ...)` annotation imported from
  `'express'` resolves to a Request whose `req.user` differs from
  the one `RequestHandler` expects internally. Type the closure as
  `RequestHandler` directly so TS infers params from the augmented
  type.

* 🩹 fix: Cast `RemoteAgentAuth` Closure to `RequestHandler`

My previous attempt removed the explicit `req: Request` annotation on
the closure to side-step the outer `RequestHandler` mismatch. That
shifted the error to every helper call site inside the closure
(`getConfigOptions(req)`, `runApiKeyAuth(req, ...)` at 467/474/493/
512/531), because the helpers annotate their params with
`express.Request` (which has the local `Request.user` augmentation),
while the unannotated closure inferred `req` as
`express-serve-static-core.Request` (no augmentation). Reproduced
locally by stubbing the gitignored `src/types/express.d.ts`.

Right approach: keep the explicit `req: Request` annotation so the
closure body matches the helpers' types, then cast at the return —
`RequestHandler`'s internal `Request` resolves through
`express-serve-static-core` and lacks the augmentation, so the cast
is the boundary that bridges the two views of `req.user`.

Verified against a build with the local express.d.ts stub: zero
warnings on `remoteAgentAuth.ts`, `env.ts`, and `google/initialize.ts`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Enhancement]: Support OAuth for Remote Agents API (configurable, switchable)

3 participants