Skip to content

🛂 fix: Reject OpenID Email Fallback When Stored openidId Mismatches Token Sub#12312

Merged
danny-avila merged 4 commits into
devfrom
fix/openid-email-fallback-guard
Mar 19, 2026
Merged

🛂 fix: Reject OpenID Email Fallback When Stored openidId Mismatches Token Sub#12312
danny-avila merged 4 commits into
devfrom
fix/openid-email-fallback-guard

Conversation

@danny-avila

@danny-avila danny-avila commented Mar 19, 2026

Copy link
Copy Markdown
Owner

Summary

Closes an account-takeover vector in the OpenID email-fallback authentication path. When the primary openidId/idOnTheSource lookup fails and findOpenIDUser falls back to an email-based query, a JWT carrying a victim's email address but a different sub claim could previously authenticate as the victim — provided the email lookup returned a result and OPENID_REUSE_TOKENS was enabled.

  • Adds a guard in findOpenIDUser (packages/api/src/auth/openid.ts) that rejects any email-matched user whose stored openidId differs from the incoming token sub, returning AUTH_FAILED immediately.
  • Positions the guard between the existing non-OpenID provider check and the migration path so that users with no openidId yet (legitimate migration candidates) are unaffected.
  • Removes a redundant && openidId middle term from the guard condition that would have silently bypassed the check when the incoming sub was an empty string or undefined, as can occur from JavaScript callers passing payload?.sub.
  • Corrects three pre-existing unit tests in openid.spec.ts whose expectations reflected the previous broken behavior (returning the matched user rather than AUTH_FAILED).
  • Adds a regression test covering the falsy openidId bypass path: openidId: '' with an email lookup returning a user with a stored openidId now correctly yields AUTH_FAILED.
  • Adds an integration test in openIdJwtStrategy.spec.js exercising the full JWT strategy verify callback with the real findOpenIDUser, asserting user === false and info.message === 'auth_failed' on mismatch.
  • Adds an integration test in openidStrategy.spec.js exercising the mismatch rejection through the full processOpenIDAuth callback, confirming createUser and updateUser are never called on rejection.
  • Restores an intent-documenting comment on the no-provider fixture to distinguish it from the provider-mismatch branch.

Change Type

  • Bug fix (non-breaking change which fixes an issue)

Testing

All three modified files have direct test coverage for the new guard. Tests use real function logic per project convention — no stub replacements of findOpenIDUser.

packages/api/src/auth/openid.spec.ts (unit — findOpenIDUser directly):

  • should reject email fallback when existing openidId does not match token sub — mismatched openidId returns AUTH_FAILED
  • should allow email fallback when existing openidId matches token sub — matching openidId returns the user
  • should reject when user already has a different openidId — pre-existing mismatch scenario
  • should reject when user has no provider but a different openidId — no-provider variant
  • should reject email fallback when openidId is empty and user has a stored openidId — falsy sub bypass regression

api/strategies/openIdJwtStrategy.spec.js (integration — JWT reuse path):

  • should reject login when email fallback finds user with mismatched openidId — full verify callback with real findOpenIDUser, mocked findUser

api/strategies/openidStrategy.spec.js (integration — standard OIDC callback path):

  • should block login when email fallback finds user with mismatched openidId — full processOpenIDAuth flow, asserts createUser/updateUser not called

Test Configuration

cd packages/api && npx jest openid
cd api && npx jest openIdJwtStrategy
cd api && npx jest openidStrategy

All 121 tests pass (20 unit + 15 JWT strategy + 86 OpenID strategy).

Checklist

  • My code adheres to this project's style guidelines
  • I have performed a self-review of my own code
  • My changes do not introduce new warnings
  • I have written tests demonstrating that my changes are effective or that my feature works
  • Local unit tests pass with my changes

…oken sub

When `findOpenIDUser` falls back to email lookup after the primary
`openidId`/`idOnTheSource` query fails, it now rejects any user whose
stored `openidId` differs from the incoming JWT subject claim. This
closes an account-takeover vector where a valid IdP JWT containing a
victim's email but a different `sub` could authenticate as the victim
when OPENID_REUSE_TOKENS is enabled.

The migration path (user has no `openidId` yet) is unaffected.
Update `findOpenIDUser` unit tests to assert that email-based lookups
returning a user with a different `openidId` are rejected with
AUTH_FAILED. Add matching integration test in `openIdJwtStrategy.spec`
exercising the full verify callback with the real `findOpenIDUser`.
Copilot AI review requested due to automatic review settings March 19, 2026 20:08

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR tightens OpenID authentication by hardening the email-fallback path in findOpenIDUser, preventing a user record found by email from being accepted when its stored openidId (subject) doesn’t match the token subject.

Changes:

  • Add a guard to reject email-fallback authentication when an existing user’s stored openidId differs from the token sub.
  • Update findOpenIDUser unit tests to cover accept/reject behavior for matching vs mismatched openidId on email fallback.
  • Add an integration-style spec in openIdJwtStrategy to ensure mismatched openidId via email fallback fails login.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
packages/api/src/auth/openid.ts Adds a rejection guard for email fallback when stored openidId mismatches token subject.
packages/api/src/auth/openid.spec.ts Updates/extends unit tests to validate the new guard and expected outcomes.
api/strategies/openIdJwtStrategy.spec.js Adds a strategy-level test ensuring mismatched subject via email fallback is rejected.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/api/src/auth/openid.ts Outdated
The `&& openidId` middle term in the guard condition caused it to be
bypassed when the incoming token `sub` was empty or undefined. Since
the JS callers can pass `payload?.sub` (which may be undefined), this
created a path where the guard never fired and the email fallback
returned the victim's account. Removing the term ensures the guard
rejects whenever the stored openidId differs from the incoming value,
regardless of whether the incoming value is falsy.
Add regression test for the guard bypass when `openidId` is an empty
string and the email lookup finds a user with a stored openidId.

Add integration test in openidStrategy.spec.js exercising the
mismatch rejection through the full processOpenIDAuth callback,
ensuring both OIDC paths (JWT reuse and standard callback) are
covered.

Restore intent-documenting comment on the no-provider fixture.
@danny-avila danny-avila changed the title fix/openid email fallback guard 🛂 fix: Reject OpenID Email Fallback When Stored openidId Mismatches Token Sub Mar 19, 2026
@danny-avila danny-avila changed the title 🛂 fix: Reject OpenID Email Fallback When Stored openidId Mismatches Token Sub 🛂 fix: Reject OpenID Email Fallback When Stored openidId Mismatches Token Sub Mar 19, 2026
@danny-avila
danny-avila merged commit 11ab5f6 into dev Mar 19, 2026
9 checks passed
@danny-avila
danny-avila deleted the fix/openid-email-fallback-guard branch March 19, 2026 20:42
@cmurtaugh

Copy link
Copy Markdown

What is the point of doing the email fallback at all? Won't it always fail?

jcbartle pushed a commit to jcbartle/LibreChat that referenced this pull request May 11, 2026
… Token Sub (danny-avila#12312)

* 🔐 fix: Reject OpenID email fallback when stored openidId mismatches token sub

When `findOpenIDUser` falls back to email lookup after the primary
`openidId`/`idOnTheSource` query fails, it now rejects any user whose
stored `openidId` differs from the incoming JWT subject claim. This
closes an account-takeover vector where a valid IdP JWT containing a
victim's email but a different `sub` could authenticate as the victim
when OPENID_REUSE_TOKENS is enabled.

The migration path (user has no `openidId` yet) is unaffected.

* test: Validate openidId mismatch guard in email fallback path

Update `findOpenIDUser` unit tests to assert that email-based lookups
returning a user with a different `openidId` are rejected with
AUTH_FAILED. Add matching integration test in `openIdJwtStrategy.spec`
exercising the full verify callback with the real `findOpenIDUser`.

* 🔐 fix: Remove redundant `openidId` truthiness check from mismatch guard

The `&& openidId` middle term in the guard condition caused it to be
bypassed when the incoming token `sub` was empty or undefined. Since
the JS callers can pass `payload?.sub` (which may be undefined), this
created a path where the guard never fired and the email fallback
returned the victim's account. Removing the term ensures the guard
rejects whenever the stored openidId differs from the incoming value,
regardless of whether the incoming value is falsy.

* test: Cover falsy openidId bypass and openidStrategy mismatch rejection

Add regression test for the guard bypass when `openidId` is an empty
string and the email lookup finds a user with a stored openidId.

Add integration test in openidStrategy.spec.js exercising the
mismatch rejection through the full processOpenIDAuth callback,
ensuring both OIDC paths (JWT reuse and standard callback) are
covered.

Restore intent-documenting comment on the no-provider fixture.
ThomasVuNguyen pushed a commit to ThomasVuNguyen/LibreChat that referenced this pull request Jul 15, 2026
… Token Sub (danny-avila#12312)

* 🔐 fix: Reject OpenID email fallback when stored openidId mismatches token sub

When `findOpenIDUser` falls back to email lookup after the primary
`openidId`/`idOnTheSource` query fails, it now rejects any user whose
stored `openidId` differs from the incoming JWT subject claim. This
closes an account-takeover vector where a valid IdP JWT containing a
victim's email but a different `sub` could authenticate as the victim
when OPENID_REUSE_TOKENS is enabled.

The migration path (user has no `openidId` yet) is unaffected.

* test: Validate openidId mismatch guard in email fallback path

Update `findOpenIDUser` unit tests to assert that email-based lookups
returning a user with a different `openidId` are rejected with
AUTH_FAILED. Add matching integration test in `openIdJwtStrategy.spec`
exercising the full verify callback with the real `findOpenIDUser`.

* 🔐 fix: Remove redundant `openidId` truthiness check from mismatch guard

The `&& openidId` middle term in the guard condition caused it to be
bypassed when the incoming token `sub` was empty or undefined. Since
the JS callers can pass `payload?.sub` (which may be undefined), this
created a path where the guard never fired and the email fallback
returned the victim's account. Removing the term ensures the guard
rejects whenever the stored openidId differs from the incoming value,
regardless of whether the incoming value is falsy.

* test: Cover falsy openidId bypass and openidStrategy mismatch rejection

Add regression test for the guard bypass when `openidId` is an empty
string and the email lookup finds a user with a stored openidId.

Add integration test in openidStrategy.spec.js exercising the
mismatch rejection through the full processOpenIDAuth callback,
ensuring both OIDC paths (JWT reuse and standard callback) are
covered.

Restore intent-documenting comment on the no-provider fixture.
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.

3 participants