Sean D
Sean D

Reputation: 4292

Passport-Local fails to auth Express Session cookie

On a call to an unprotected route I am able to print and respond with the values at req.cookies

{
    "cookies": {
        "connect.sid": "s:PqWvDsoKLMeCyRd8pGN<removed>",
        "testCookie": "testValue"
    },
    "signedCookies": {}
}

However this route returns 401 Unauthorized

router.get('/authCheck', passportWithLocalStrategy.authenticate('local'), (req: Request, res: Response, next: NextFunction) => {
  res.status(204).send();
});

Does anyone know what could be causing the session cookie not to authenticate?

Here is my main server.ts file, de/serialization, and the middleware stack:

// called to set a cookie initially
passport.serializeUser((user: any, callback) => {
  callback(null, user.id as string);
});

// called every time a request is made
passport.deserializeUser(async (userId: string, callback) => {
  const pgClient = new PgClient();
  try {
    pgClient.connect();
    const userRecord = (await pgClient.query('SELECT * FROM app_user WHERE CAST(id as text) = CAST($1 as text)', [userId])).rows[0];
    pgClient.end();
    callback(null, userRecord);
  } catch (error) {
    callback(error);
  }
});

server
  .use(cors())
  .use(express.json())
  .use(expressSession({ secret: process.env.SESSION_SECRET! }))
  .use(cookieParser())
  .use(passport.initialize())
  .use(passport.session())
  .use(rootRouter)
  <other routers removed>

I have setup my Passport LocalStrategy as shown:

async function useDatabaseToVerifyUserAndPassword(localUserName: string,
  localPassword: string, doneCallback: any) {
  const pgClient = new PgClient();

  try {
    await pgClient.connect();
    const queryResult = await pgClient.query(selectUserQuery, [localUserName]);
    pgClient.end();
    const userData: UserMatch = queryResult.rows[0];

    if (typeof userData === 'undefined' || typeof userData.password_hash === 'undefined') {
      return doneCallback(null, false);
    }

    const hashesMatch: boolean = await bcrypt.compare(localPassword, userData.password_hash);

    if (hashesMatch) {
      return doneCallback(null, userData);
    }

    return doneCallback(null, false); // username not found or passHash mismatch. Prints 401 UnAuth
  } catch (error) {
    return doneCallback(error, false);
  }
}

const strategyOptions = {
  usernameField: 'localUserName',
  passwordField: 'localPassword',
};

const localStrategy = new LocalStrategy(strategyOptions, useDatabaseToVerifyUserAndPassword);
passport.use(localStrategy);

export default passport;

The above export is brought into the router file (for the route at /authCheck) as passportWithLocalStrategy. If I just import passport from the library folder to that file, the route breaks, hanging indefinitely.

Update

I have tried unprotecting the route and accessing req.isAuthenticated(). It always returns false even when the session cookie is there.

I see this information printed when logging req.session

Session Session {
  cookie: { path: '/', _expires: null, originalMaxAge: null, httpOnly: true }  

Upvotes: 1

Views: 75

Answers (1)

jasonandmonte
jasonandmonte

Reputation: 2028

You should have a separate login route that calls passport.authenticate after that returns successfully passport will add req.session.passport value with the serialized userId. You only need to do this process once when logging in a user.

The /authCheck route can then be refactored with middleware that just checks that the user is still logged in.

router.get('/authCheck', (req: Request, res: Response, next: NextFunction) => {
  // passport isAuthenticated method
  if(req.isAuthenticated()){
        //req.isAuthenticated() will return true if user is logged in
        return res.status(204).send();
    } else{
        res.redirect("/login");
    }
});

Upvotes: 1

Related Questions