Why is the set method in my custom Redis session store being triggered twice?

55 Views Asked by At

I am developing a web application using NestJS, Passport, express-session, and I've implemented custom session management using Redis (ioredis). I've noticed that the set method in my custom RedisStore is being triggered twice for a single user session creation, and I cannot figure out why this is happening. All other methods are triggered only once.

I have a LocalAuthGuard that extends AuthGuard('local') from @nestjs/passport, which is used for authentication:

import { ExecutionContext, Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class LocalAuthGuard extends AuthGuard('local') {
  async canActivate(context: ExecutionContext) {
    const result = (await super.canActivate(context)) as boolean;
    const request = context.switchToHttp().getRequest();

    await super.logIn(request);
    return result;
  }
}

My session is configured as follows in the bootstrap function:

app.use(
    session({
      name: 'dekada_session',
      store: new RedisStore(redisService),
      secret: 'keyboard cat',
      resave: false,
      saveUninitialized: false,
      rolling: false,
      cookie: {
        maxAge: 3600000,
        secure: false,
        sameSite: 'lax',
        httpOnly: true,
      },
    }),
  );

This is the simplified version of set

set(sid: string, session: SessionData, callback?: (error?: any) => void): void {
  console.log('set');
  const ttl = 3600;
  this.redisService.getClient().setex(`sess:${sid}`, ttl, JSON.stringify(session), (error) => {
    callback(error);
  });
}

Despite sending only one request from Postman, the set method is logged twice, indicating its being called twice. This happens during the login process, where a session should be created only once. I verified that the serializeUser and deserializeUser methods of Passport are called only once, and the same applies to the canActivate method in my LocalAuthGuard.

That's how console logs are triggered:

  1. get
  2. destroy
  3. set
  4. [Function: serialized]
  5. set
1

There are 1 best solutions below

0
Nazar Duma On

After inspecting passport code, found roots of double set invocation.

/lib/sessionmanager.js

SessionManager.prototype.logIn = function(req, user, options, cb) {
  if (typeof options == 'function') {
    cb = options;
    options = {};
  }
  options = options || {};
  
  if (!req.session) { return cb(new Error('Login sessions require session support. Did you forget to use `express-session` middleware?')); }
  
  var self = this;
  var prevSession = req.session;
  
  // regenerate the session, which is good practice to help
  // guard against forms of session fixation
  req.session.regenerate(function(err) {
    if (err) {
      return cb(err);
    }
    
    self._serializeUser(user, req, function(err, obj) {
      if (err) {
        return cb(err);
      }
      if (options.keepSessionInfo) {
        merge(req.session, prevSession);
      }
      if (!req.session[self._key]) {
        req.session[self._key] = {};
      }
      // store user information in session, typically a user id
      req.session[self._key].user = obj;
      // save the session before redirection to ensure page
      // load does not happen before session is saved
      req.session.save(function(err) {
        if (err) {
          return cb(err);
        }
        cb();
      });
    });
  });
}

This code regenerates the session as a security measure against session fixation attacks by creating a new session ID while preserving session data. After regeneration, it explicitly saves the session again. However, express-session seems to notice the session update and triggers another set operation automatically at the end of the request-response cycle.

While I understand the necessity of session regeneration for security purposes, I'm looking to optimize session handling by eliminating the redundant set call made by express-session. This redundancy seems unnecessary and could potentially impact performance.

Question: Is there a way to configure express-session or Passport to prevent this additional session set operation while still maintaining the integrity and security of the session, especially in the context of preventing session fixation attacks?