Skip to content

fix: preserve multi-instance session data on login/logout with tests …#1394

Open
Tony133 wants to merge 5 commits into
mainfrom
fix/session-persistence-multi-instance-and-type-safe-test
Open

fix: preserve multi-instance session data on login/logout with tests …#1394
Tony133 wants to merge 5 commits into
mainfrom
fix/session-persistence-multi-instance-and-type-safe-test

Conversation

@Tony133

@Tony133 Tony133 commented Apr 7, 2026

Copy link
Copy Markdown
Member

Proposal:

When using multiple Authenticator instances with clearSessionOnLogin: false, logging in or out with one instance would inadvertently destroy the session data of other instances.

This happened because request.session.regenerate() was called without arguments in both logIn and logOut, which wipes the entire session — including passport keys belonging to other instances (e.g. passport-a being lost when logging in via passport-b).

Changes Made:

  • In SecureSessionManager:

    • logIn: the else branch (triggered when clearSessionOnLogin: false) called request.session.regenerate() with no arguments, destroying all existing session data.

    • logOut: similarly called request.session.regenerate() unconditionally, logging out all instances instead of just the current one.

    • When clearSessionOnLogin is false, instead of calling regenerate() blindly:

      • Read the existing session data via (request.session as any).data?.() before regenerating
      • Call request.session.regenerate() to get a new secure session ID
      • Restore the saved data with request.session.set(), omitting only the key being logged out (in logOut)
  • Updated tests with better type checking

  • Update @fastify/secure-session dependency in the package.json

Note:

  • session.data() is used because Object.keys(request.session) only returns internal metadata fields (changed, deleted), not the actual session data. The method is available on both @fastify/session and @fastify/secure-session, though only declared in the types of the latter.

This PR should close this PRs:

@Tony133 Tony133 marked this pull request as ready for review April 7, 2026 21:02
@Tony133 Tony133 force-pushed the fix/session-persistence-multi-instance-and-type-safe-test branch from b387ac4 to ca88732 Compare April 23, 2026 07:08
Tony133 added 2 commits April 23, 2026 09:11
…ype-safe-test

Signed-off-by: Antonio Tripodi <Tony133@users.noreply.github.com>
@Tony133 Tony133 requested a review from a team May 2, 2026 20:41
request.session.set(this.key, undefined)
if (request.session.regenerate) {
await request.session.regenerate()
if (this.clearSessionOnLogin) {

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.

I would still keep the regenerate as it was, considering that it is invoked in both the branches, and then transform the else in if(!this.clearSessionOnLogin)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I would still keep the regenerate as it was, considering that it is invoked in both the branches, and then transform the else in if(!this.clearSessionOnLogin)

I tried it but unfortunately it breaks the tests "logging out with one instance should not log out the other instance" and "multiple registered instances (clearSessionOnLogin: false)"

The issue is that data() must be called before of regenerate(), because after regeneration the session is brand new and the existing data is already lost. Moving regenerate() before the condition makes it impossible to restore the other instance's session data.

So the if/else structure is actually necessary here to ensure we read existingData before calling regenerate().

We could refactor the plugin a bit, perhaps in another pull request

Comment thread test/authentication-route.test.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Fixes a session-isolation bug when multiple Authenticator instances share the same @fastify/session session and clearSessionOnLogin: false, where session.regenerate() was wiping unrelated passport namespaces during login/logout.

Changes:

  • Preserve and restore existing session data around session.regenerate() in SecureSessionManager.logIn() / logOut() when clearSessionOnLogin is false.
  • Update many tests to satisfy stricter Fastify/TS typing for preValidation hooks.
  • Bump @fastify/secure-session devDependency to ^8.3.0.

Reviewed changes

Copilot reviewed 17 out of 18 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/session-managers/SecureSessionManager.ts Preserve existing session key/value pairs across regeneration to avoid cross-instance session data loss.
test/multi-instance.test.ts Exercises the multi-authenticator scenario (login/logout isolation) and updates hook typing.
test/session-isolation.test.ts Tightens session-isolation tests and updates hook typing.
test/secure-session-manager.test.ts Improves type safety in unit tests for SecureSessionManager.
test/passport.test.ts Updates route declarations to satisfy hook typing; continues broad passport behavior coverage.
test/strategy.test.ts Updates preValidation typing for authentication hook usage.
test/strategies-integration.test.ts Updates preValidation typing for OAuth integration strategy tests.
test/session-serialization.test.ts Updates preValidation typing in serialization flow tests.
test/multi-strategy-callback.test.ts Updates typing around multi-strategy authenticate callbacks used in preValidation.
test/independent-strategy-instances.test.ts Updates preValidation typing for independent strategy instance tests.
test/decorators.test.ts Updates preValidation typing for decorator behavior tests.
test/csrf-fixation.test.ts Updates preValidation typing in CSRF fixation regression test.
test/base-strategy.test.ts Updates preValidation typing in base-strategy coverage test.
test/authorize.test.ts Updates preValidation typing for authorize() hook usage.
test/authenticator.test.ts Updates preValidation typing in authenticator edge-case tests.
test/authentication-route.test.ts Updates preValidation typing in authentication-route edge-case tests.
package.json Bumps @fastify/secure-session to ^8.3.0.

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

Comment on lines 48 to +52
keepSessionInfoKeys.push(...Object.keys(request.session))
}
await request.session.regenerate(keepSessionInfoKeys)
} else {
const existingData: Record<string, unknown> = (request.session as any).data?.() ?? {}
Comment on lines +52 to +56
const existingData: Record<string, unknown> = (request.session as any).data?.() ?? {}
await request.session.regenerate()
for (const [key, value] of Object.entries(existingData)) {
request.session.set(key, value)
}
Comment thread test/passport.test.ts
Comment on lines +16 to +20
{
preValidation: fastifyPassport.authenticate('test', {
authInfo: false
}) as unknown as preValidationHookHandler
},
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