Search code examples
node.jsexpressjestjspassport.jssuperagent

Cookies persist in supertest/superagent test, but the user doesn't stay logged in


My Goal

I'm trying to use supertest's agent function in a jest beforeEach() to login the user before each test, as I want each test to run under the assumption that the user is signed in. For authentication, I am using passport and passport-local.


This is what I tried (with parts cut out for brevity):

Test file:

import { agent, SuperAgentTest } from 'supertest';
import app from '../../src/app';

// create a `testRequest` variable to use in the tests
// that will be refreshed in between
let testRequest: SuperAgentTest;

const fakeUser = { email: 'john@john', username: 'john', password: 'john' };

beforeEach(async () => {
  // create new agent
  testRequest = agent(app);

  // register and login
  await testRequest.post('/register').send(fakeUser).expect(302);

  // other irrelevant stuff...
});

// protected route
describe('POST /campgrounds/new', () => {
  it('returns 200 OK', () => {
    return testRequest.get('/campgrounds/new');
  })
});

/register route:

router.post('/register', async (req, res) => {
  const { password, ...details } = req.body;
  try {
    // I am using passport-local-mongoose for this function-
    // it just registers the user
    const user = await User.register(new User(details), password);
    req.login(user, (err) => {
      // error handling and redirect
    });
  } catch (e) {
    // error handling
  }
})

This is my result

Instead of a 200 status, I get a 302 status, meaning I was redirected to the login page. To debug this, I created a test route called /current which will log the current user and session ID cookie. I then sent a GET request to this route in both the it and beforeEach function respectively.

Interestingly, they both logged the same session ID, but only the request in beforeEach had a user object attached to the request.


Solution

  • #1 Ensure body parser correct order

    Make sure you have this before any routes or auth-related things.

    app.use(express.json())
    

    #2 Check Passport Middleware Wire-up

    Ensure you call app.use(passport.initialize()) & app.use(passport.session()) before any app.use('/', aRouter), router.get, router.post, etc:

    // Set up session w/ specific config
    app.use(session({
      secret: 'bquyqueajhbd',
      resave: true,
      saveUninitialized: true,
      store: new FileStore({path: '/tmp/session'})
    }));
    // Wire up the 
    app.use(passport.initialize())
    app.use(passport.session())
    

    EDIT: Notes on req.user

    Passport is designed to store the user ID in session.

    Every request to the server must reload the user from the database. This is the job of the middleware passport.initialize() and passport.session().

    The logic there will call passport.deserializeUser to lookup the user by ID - the same ID that was saved upon login into the session by passport.serializeUser.

    passport.serializeUser(function(user, done) {
      done(null, user.id); // <-- Here's where the ID is saved to session.
    });
    
    passport.deserializeUser(function(id, done) {
      User.findById(id, function(err, user) {
        done(err, user); // <-- Here is where the `req.user` get's it's value from.
      });
    });
    

    To debug this I'd focus on the passport.deserializeUser callback, add logs before and after the DB query.

    (Note: it's been a few years since I taught this. Appologies if I'm not using the precise terms, etc.)