I am writing a client/server application where the frontend is using React and Axios while the backend is using Express, MySQL and PassportJS. I have a running MySQL database with a users-table, and in the app I can register users successfully with their passwords being hashed and stored in the database. I have also implemented PassportJS to successfully authorize logins. After logging in with a correct username/password combination, a cookie with the following form is saved in the browser:
userId:
s:"qAxPZ8u77YA7_NRSk2sfLxltZI3D5klX.5okMTprFTBBq4RFwyCd2ptkAfv9dfL9Z7IViSK5bGpg"
I don't know if anything seems incorrect about the above cookie. It has some other properties of course like expires etc., but no obvious "user"-object or similar except for the string saved to userId.
Main problem is when I try to check whether a user is logged in to the app on the server side. I use axios on the client side, setup like this:
import axios from 'axios';
axios.defaults.baseURL = 'http://localhost:3000/api/v1';
axios.defaults.withCredentials = true;
And from the login-module (could be anyone but I used this to test) of my React app I call on a method I have saved which looks like this:
login() {
return axios.get("/login").then((response) => {
console.log(response);
});
}
This sends the request to the backend which is set up like this: index.ts
import express from 'express';
import router from '../src/router';
import path from 'path';
import db from '../src/mysql-pool';
require('dotenv').config()
const app = express();
const passport = require('passport');
const flash = require('express-flash');
const session = require('express-session');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
app.use(express.static(path.join(__dirname, '/../../client/public')));
app.use(express.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cookieParser(process.env.SESSION_SECRET));
app.use(flash());
app.use(session({
key: "userId",
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
expires: 86400000 // ms, aka 24 hours
}
}));
app.use(passport.initialize());
app.use(passport.session());
const initializePassport = require('./passport-config');
initializePassport(
passport,
(username: string) => {
return new Promise<{}>((resolve, reject) => {
db.query('SELECT * FROM users WHERE username = ?', [username], (error, results) => {
if (error) return reject(error);
if (!(results.length > 0)) {
return reject({ message: "User doesn't exist." });
}
resolve(results[0]);
});
});
},
(id: number) => {
return new Promise<{}>((resolve, reject) => {
db.query('SELECT * FROM users WHERE user_id = ?', [id], (error, results) => {
if (error) return reject(error);
if (!(results.length > 0)) {
return reject({ message: "User doesn't exist." });
}
resolve(results[0]);
});
});
}
);
// the method being called from the client side
router.get('/login', (request, response) => {
console.log(request.user);
if (request.user) {
response.send({ loggedIn: true, user: request.user });
} else {
response.send({ loggedIn: false })
}
});
router.post('/login', passport.authenticate('local', {
successRedirect: '/',
failureRedirect: '/login',
failureFlash : true
}));
app.use('/api/v1', router);
const port = 3000;
app.listen(port, () => {
console.info(`Server running on port ${port}`);
});
The router is declared in another file called router.ts:
import express from 'express';
import { registerService, loginService } from './services'; // file with some MySQL database queries
require('dotenv').config()
const bcrypt = require('bcrypt');
const saltRounds = 10;
const router = express.Router();
// [.. some unrelated and unproblematic api-calls and routes omitted here ..]
router.post('/register', (request, response) => {
const username = request.body.username;
const password = request.body.password;
if (username && username.length != 0) {
bcrypt.hash(password, saltRounds, (error: Error, hash: string) => {
if (error) response.status(500).send(error);
registerService.register(username, hash)
.then(() => response.status(201).send('New user registered'))
.catch((error) => response.status(500).send(error));
});
} else response.status(400).send("Can't register user: Missing username og password.");
});
export default router;
And passport is configured in a file called passport-config.ts:
// @ts-nocheck
const LocalStrategy = require('passport-local').Strategy;
const bcrypt = require('bcrypt');
function initialize(passport, getUserByUsername, getUserById) {
const authenticateUser = async (username, password, done) => {
const user = await getUserByUsername(username);
if (user == null) {
return done(null, false, { message: 'No user exists that matches the given username.' })
}
try {
if (await bcrypt.compare(password, user.passwd)) {
return done(null, user)
} else {
return done(null, false, { message: 'Wrong username or password.' })
}
} catch (error) {
return done(error)
}
}
passport.use(new LocalStrategy({ usernameField : 'username', passwordField : 'password'}, authenticateUser));
passport.serializeUser((user, done) => done(null, user.user_id));
passport.deserializeUser((id, done) => {
return done(null, getUserById(id));
});
};
module.exports = initialize;
Now: The PassportJS docs and most guides I have tried to follow say that user data after authenticating is stored as req.user. But this route on the backend:
router.get('/login', (request, response) => {
console.log(request.user);
if (request.user) {
response.send({ loggedIn: true, user: request.user });
} else {
response.send({ loggedIn: false })
}
});
returns undefined.
If I instead call:
router.get('/login', (request, response) => {
console.log(request.session); // console log the session instead of req.user
if (request.user) {
response.send({ loggedIn: true, user: request.user });
} else {
response.send({ loggedIn: false })
}
});
It logs:
Session {
cookie: {
path: '/',
_expires: 86400000,
originalMaxAge: 86400000,
httpOnly: true
}
}
So something is definetly happening.
It just doesn't seem like Passport ever saved the user data in the session after authenticating, or if it did, I can't find it.
Any help would be greatly appreciated, I can include more info as well if needed.
EDIT: For future reference, the solution ended up having something to do with the order of the commands in index.ts. If I remember correctly I think the app.use(passport.xxx)-commands had to be above alot of the other commands. I am afraid I can't recall exactly what order fixed the issue.