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.
Related
I know that there are some questions like this already, but i already tried every single response and nothing works, i don't know what the heck to add to app.js to make it work and store the sessions.
Here is my app.js:
const express = require("express");
const cors = require("cors");
const usersRouter = require("./routes/users");
const passport = require("passport");
const cookieParser = require("cookie-parser");
const session = require("express-session");
require("dotenv").config();
const app = express();
const connectDB = require("./db/connect");
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser(process.env.SESSION_SECRET));
app.use(
session({
secret: process.env.SESSION_SECRET,
resave: true,
saveUninitialized: true,
cookie: { secure: false },
})
);
app.use(passport.initialize());
app.use(passport.session());
app.use(cors());
app.use("/api/v1/users", usersRouter);
const PORT = process.env.PORT || 5000;
const start = async () => {
try {
await connectDB(process.env.MONGO_URI);
app.listen(PORT, console.log(`Server is listening on port: ${PORT}....`));
} catch (error) {
console.log(error);
}
};
const notFound = require("./middleware/notFound");
app.use(notFound);
start();
My passport-config looks like this:
const localStrategy = require("passport-local").Strategy;
const bcrypt = require("bcryptjs");
const User = require("../models/user");
const initialize = (passport) => {
const authenticateUser = async (email, password, done) => {
let user = await User.findOne({ email: email });
User.findOne({ email: email });
if (!user) {
return done(null, false, {
message: "That email is not registered",
});
}
try {
if (await bcrypt.compare(password, user.password)) {
return done(null, user, { message: "User logged in" });
} else {
return done(null, false, { message: "Password incorrect" });
}
} catch (e) {
return done(e);
}
};
passport.use(new localStrategy({ usernameField: "email" }, authenticateUser));
passport.serializeUser(function (user, done) {
done(null, user.id);
});
passport.deserializeUser(function (user, done) {
User.findById(id, function (err, user) {
done(err, user);
});
});
};
module.exports = initialize;
And my users router looks like this:
const express = require("express");
const router = express.Router();
const passport = require("passport");
const initializePassport = require("../config/passport-config");
initializePassport(passport);
const { postRegister } = require("../controllers/register");
router.route("/register").post(postRegister);
router.post("/login", function (req, res, next) {
passport.authenticate("local", function (err, user, info) {
if (err) {
return next(err);
}
if (!user) {
return res.status(401).json({ message: info.message });
}
console.log(req.session.passport);
res.status(200).json(user);
})(req, res, next);
});
module.exports = router;
I'm making a middleware to authorize or to not authorize the call to another endpoint based on if it is authenticated or if it isn't.
Here is how that middleware looks:
const checkAuthenticated = (req, res, next) => {
const isAuthenticated = req.isAuthenticated();
console.log(req.session.passport);
if (isAuthenticated) {
next();
}
next();
};
module.exports = checkAuthenticated;
const checkNotAuthenticated = (req, res, next) => {
const isAuthenticated = req.isAuthenticated();
if (!isAuthenticated) {
res
.status(401)
.json({ msg: "Not allowed to this path without credentials" });
}
};
module.exports = checkNotAuthenticated;
req.session.passport is undefined, isAuthenticated() is always false, i don't know what to add.
Your bug in the deserializer function:
It will work by changing your deserializer callback argument from user to id as shown below.
passport.deserializeUser(function (id, done) {
User.findById(id, function (err, user) {
done(err, user);
});
That was a very obvious bug to me, to be sure you are not missing any other thing, check the lengthy answer I gave to this on another thread a few minutes ago here: https://github.com/jaredhanson/passport/issues/914#issuecomment-1241921637
Good luck.
I have been in the process of building a server using node.js, express.js, Mongoose, and MongoDB. After trying to implement passport and JWT authentication, I have encountered an error (seen in the link) every time I try to make a request to my '/login' endpoint. This error occurs when I make a request through Postman using a username and password from my database to test if the user is assigned a JWT token.
https://i.stack.imgur.com/g6e4Z.png
All of my endpoints, except for the one in my auth.js file, work fine in my index.js file, which is the main file where everything gets imported. I do not have any problems getting/staying connected to my database, so this leads me to believe that the issue stems from some mistake in the error handling.
This is how the middleware is set up in the index.js file, right above my endpoints.
'use strict';
const express = require('express');
const morgan = require('morgan');
const mongoose = require('mongoose');
const Models = require('./models');
const Movies = Models.Movie;
const Genres = Models.Genre;
const Directors = Models.Director;
const Users = Models.User;
function displayErrorMsg(err) {
console.error(err);
res.status(500).send(`Error: ${err}`);
}
function resJSON(model, res) {
return model.find().then(data => res.json(data));
}
const app = express();
mongoose
.connect('mongodb://localhost:27017/filmfeverDB', {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(res => console.log('DB Connected!'))
.catch(err => console.log(err, err.message));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.static(`public`));
app.use(morgan('common'));
let auth = require('./auth')(app);
const passport = require('passport');
require('./passport');
passport.js
const passport = require("passport"),
LocalStrategy = require("passport-local").Strategy,
Models = require("./models.js"),
passportJWT = require("passport-jwt"),
config = require('./configs').config
let Users = Models.User,
JWTStrategy = passportJWT.Strategy,
ExtractJWT = passportJWT.ExtractJwt;
passport.use(
new LocalStrategy(
{
usernameField: "Username",
passwordField: "Password"
},
(username, password, callback) => {
console.log(username + " " + password);
Users.findOne({ Username: username }, (error, user) => {
if (error) {
console.log(error);
return callback(error);
}
if (!user) {
console.log("incorrect username");
return callback(null, false, {
message: "Incorrect username."
});
}
console.log("finished");
return callback(null, user);
});
}
)
);
passport.use(
new JWTStrategy(
{
jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(),
secretOrKey: config.passport.secret
},
(jwtPayload, callback) => {
return Users.findById(jwtPayload._id)
.then(user => {
return callback(null, user);
})
.catch(error => {
return callback(error);
});
}
)
);
auth.js
const jwt = require("jsonwebtoken"),
passport = require("passport");
require("./passport");
let generateJWTToken = user => {
return jwt.sign(user, config.passport.secret, {
subject: user.Username,
expiresIn: config.passport.expiresIn,
algorithm: "HS256"
});
};
/* POST login. */
module.exports = router => {
router.post("/login", (req, res) => {
passport.authenticate("local", { session: false }, (error, user, info) => {
if (error || !user) {
return res.status(400).json({
message: "something is not right",
user: user
});
}
req.login(user, { session: false }, error => {
if (error) {
res.send(error);
}
let token = generateJWTToken(user.toJSON());
return res.json({ user, token });
});
})(req, res);
});
};
Here is my Mongoose connection and my imports of the auth.js, passport.js, and passport module files located in my index.js file.
mongoose
.connect('mongodb://localhost:27017/filmfeverDB', {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(res => console.log('DB Connected!'))
.catch(err => console.log(err, err.message));
let auth = require('./auth')(app);
const passport = require('passport');
require('./passport');
If anyone has any ideas, please let me know.
I'm trying to implement passport.js's local strategy for authentication. My server is using express routes (I believe this might be the cause of the problem).
If I try to pass anything on the frontend with Axios, the server throws me the error "Missing credentials", even though console logs on the server show me correct data was received.
My main server file:
import express from 'express';
import mongoose from 'mongoose';
import cors from 'cors';
import passport from 'passport';
import cookieParser from 'cookie-parser';
import session from 'express-session';
import authStrategy from "./utils/passportConfig";
// enables ENV variables to be present under command proccess.env
require('dotenv').config();
// enables express.js on the app and defines the port
const app = express();
const port = process.env.PORT || 5000;
// enables CORS - cross origin resource sharing -
// makes possible requesting resources from another domain
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cors());
const secret = process.env.SECRET;
app.use(session({
secret: secret!,
resave: true,
saveUninitialized: true,
}))
app.use(cookieParser(secret));
app.use(passport.initialize());
app.use(passport.session());
authStrategy(passport);
// mongoose helps us connect to our MongoDB database
const uri = process.env.MONGO_URI!;
mongoose.connect(uri, { useNewUrlParser: true, useCreateIndex: true });
const connection = mongoose.connection;
connection.once('open', () => {
console.log("MongoDB databse connection established successfully");
});
// Routes
const usersRouter = require('./routes/users')(passport);
app.use('/user', usersRouter);
// starts the server, and listens for changes on predefined port
app.listen(port, () => {
console.log(`Server is running on port: ${port}`);
});
My passport local strategy configuration:
import User, { IUser } from "../models/user.model";
import bcrypt from "bcrypt";
import * as passportLocal from "passport-local";
import { PassportStatic } from "passport";
const localStrategy = passportLocal.Strategy;
export default module.exports = (passport: PassportStatic) => {
passport.use(
new localStrategy({
usernameField: "email",
},
(email, password, done) => {
User.findOne({ email: email.toLowerCase() }).exec()
.then((user) => {
if (!user) {
return done(null, false, { message: "User with that email does not exist"});
}
bcrypt.compare(password, user.password, (err) => {
if (err) {
return done(null, false, { message: "Incorrect password" });
}
return done(null, user);
})
})
.catch((err) => {
return done(err);
})
})
)
passport.serializeUser((req: any, user: any, done: any) => {
done(null, user.id);
})
passport.deserializeUser((id, done) => {
User.findOne({_id: id}, (err: any, user: any) => {
done(err, user);
});
})
}
My express route handling:
import express from "express";
import { PassportStatic } from "passport";
module.exports = (passport: PassportStatic) => {
const router = express.Router();
router.route('/login').post( function (req, res, next) {
console.log("####", req.body)
passport.authenticate('local', function (err, user, info) {
console.log("MMMM", err, user, info)
if (err) { return next(err); }
if (!user) { return res.status(500).json("User does not exist") }
req.logIn(user, err => {
if (err) {
return next(err);
}
res.status(200).json("Successfully authenticated");
console.log(req.user);
})
})(res, res, next);
});
return router;
}
What console logs on login router return:
#### { email: 'stackoverflow#test.com', password: 'fakepassword' }
MMMM null false { message: 'Missing credentials' }
All solutions to this problem I have found say, that Missing credentials error is due to passport authentication strategy expecting username and password key, and that if you pass anything else, you need to let passport know. As you can see in the passport strategy config file, I have done that.
passport.use(
new localStrategy({
usernameField: "email",
},
(email, password, done) => {
...
What might be the issue here? Is passport not being passed correctly from server.ts? Does the passport not use configured local strategy?
Used packages versions:
express: 4.17.1 |
express-session: 1.17.1 |
passport: 0.4.1 |
passport-local: 1.0.0 |
Edit: Issue solved
There is a problem in the express route handling file, I'm passing `req` twice in the authentication middleware.
I have marked the first reply as correct, as that is the solution most people need when they receive Missing credentials error.
It seems like you have missed the password field while configuring the passport local strategy.
It must look something like this:
passport.use(
"local",
new LocalStrategy(
{
usernameField: "email",
passwordField: "password",
passReqToCallback: false, //optional
},
async (username, password, done) => {
...
I'm starting with Nuxt JS trying to migrate an old site with passport local and express-session, I get to make the authentication based on this repository but the main problem comes when I reload the page the users logout, its appears that Nuxt JS it's not saving my user session on the browser. I mostly sure there is something that im forgetting to implement or im not understanding. How could I save my res session token on my user browser?. Btw I have my API running on a separate port, so im not sure if there is any problem on saving session from other port. Here is my code:
Login.vue
<script>
import axios from 'axios';
export default {
data() {
return {
error : false,
form : {
username: '',
password: ''
}
}
},
created() {
if(this.$store.state.user) {
return this.$router.push('/');
}
},
methods: {
async login () {
await this.$store.dispatch('login', {
username : this.form.username,
password: this.form.password
});
this.form.password = '';
this.form.username = '';
}
},
}
</script>
Store/Index.js:
import axios from "axios";
export const state = () => ({
user: null,
});
export const mutations = {
SET_USER(state, user) {
state.user = user;
},
};
export const actions = {
nuxtServerInit({ commit }, { req }) {
if (req) {
if (
typeof req.session !== "undefined" &&
typeof req.user !== "undefined"
) {
commit("SET_USER", req.user);
}
}
},
login({ commit }, { username, password }) {
return axios({
method: "post",
url: this.$axios.defaults.baseURL + "/auth/login",
credentials: "same-origin",
data: {
username,
password
}
})
.then(res => {
if (res.data.meta.error === true) {
throw res.data;
}
return res.data.user;
})
.then(authUser => {
commit("SET_USER", authUser);
});
},
};
API Index.js
const express = require('express');
const app = express();
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const helmet = require('helmet');
const cors = require('cors') // Srsly. Fuck Cors
const morgan = require('morgan');
const session = require('express-session');
const MySQLStore = require('express-mysql-session')(session);
const passport = require('passport');
const { database } = require('./config/keys');
const config = require('./config/config.json');
require('./lib/bAuth');
app.disable('view cache');
app.disable('x-powered-by');
app.set('port', process.env.PORT || config.port);
app.use(helmet());
app.use(cors());
app.use(morgan('dev'));
app.use(express.json());
app.use(bodyParser.urlencoded({extended: false}));
app.use(bodyParser.json());
app.use(cookieParser());
app.use(session({
name: '_hsid',
key: 'sessionKey',
secret: config.session_secret,
resave: false,
saveUninitialized: false,
store: new MySQLStore(database),
cookie : {
maxAge: 1000 * 60 * 60 *24 * 365,
},
})
);
app.use(passport.initialize());
app.use(passport.session());
app.listen(app.get('port'), () => console.log(`[✔] Website connected and running at port: ${app.get('port')}`))
Login Route:
app.post('/login', async (req,res,next) => {
req.query = req.body;
auth.authenticate('user-login', (err,user,info) => {
if(user){
req.logIn(user,(err)=>{
if(err){
return res.json({
meta: {
error: true,
msg: err
}
});
}else{
if(req.isAuthenticated()){
if(!req.user.authenticated){
return res.json({
meta: {
error: true,
msg: "Bad credentials"
}
});
}else{
return res.json({
meta: {
error: false
},
user: bAccess.cleanUser(Object.assign({}, user))
});
};
}
}
});
}else{
return res.json({
meta: {
error: true,
msg: "Bad credentials"
}
});
}
})(req,res,next);
});
Passport Config:
auth.use('user-login', new Strategy({
usernameField: 'username',
passwordField: 'password',
passReqToCallback: true
}, async (req, username, password, done) => {
const _user = await _context.query('select username,password from users where username = ?', username);
if (!_user[0]) return done(null,false);
if (!_user[0].password || !await bAccess.verifyPassword(password, _user[0].password)) {
return done(null, false);
}
done(null, _user[0], {scope : '*'});
}, ));
auth.serializeUser(function (user, done) {
done(null, user);
});
auth.deserializeUser(async (user, done) => {
done(null, user);
});
I am trying to implement simple authentication with Node, Express, Express-Session and Passport.js. As a storage middleware I'm using connect-pg-simple as I am using pg-promise in my project.
I managed to configure passport to work with my routers but for some reason sessions won't be saved to my database. None of the plugins return any errors, everything seems to be running smoothly but my sessions table is empty.
What could I be doing wrong? connect-pg-simple connects to the server, passport.js is holding sessions and express-session assigns configured maxAge to cookies.
server.js
const express = require('express');
const db = require('./database.js');
const cors = require('cors');
const helmet = require('helmet');
const session = require('express-session');
const passport = require('passport');
const port = process.env.PORT || 3000;
const app = express();
const pgSession = require('connect-pg-simple')(session);
const pgStoreConfig = {
pgPromise: db.conn
};
app.set('trust proxy', 1);
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(
session({
secret: REDACTED, // need to change it later to some proper hash
store: new pgSession(pgStoreConfig),
resave: true,
cookie: {
maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days ;)
secure: app.get('env') === 'production'
},
saveUninitialized: false
})
);
app.use(passport.initialize());
app.use(passport.session());
app.use(cors());
app.use(helmet());
app.get('/', (req, res) => {
if (req.session.views) {
req.session.views++;
} else {
req.session.views = 1;
}
//res.send(`Views: ${req.session.views}`);
res.json(req.session);
});
const userRouter = require('./routes/user.js');
app.use(userRouter);
config/passport.js
const passport = require('passport');
const local = require('passport-local');
const db = require('../database.js');
const statements = require('../routes/statements/user.js');
const cryptoUtils = require('../utils/crypto.js');
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser(async (id, done) => {
try {
let result = await db.conn.one(statements.getUserByIdStatement, [id]);
done(null, result);
} catch (error) {
console.log(`Error while deserializing user: ${error}`);
return done(error);
}
});
passport.use(
new local.Strategy(
{ usernameField: 'email', passwordField: 'password' },
async (username, password, done) => {
try {
let user = await db.conn.one(statements.loginUserStatement, [username]);
if (user == null) {
console.log(`Couldn't find user!`);
return done(null, false);
} else {
let passwordCheck = cryptoUtils.comparePasswords(
password,
user.password,
user.salt
);
if (passwordCheck) {
delete user.password;
delete user.salt;
console.log(`Successfuly logged in!`);
return done(null, user);
} else {
console.log(`Wrong password!`);
return done(null, false);
}
}
} catch (error) {
console.log(`Error during local strategy authentication: ${error}`);
return done(null, false);
}
}
)
);
module.exports = passport;
routes/user.js
router.post('/user/login', (req, res, next) => {
console.log('Authenticating');
passport.authenticate('local', (err, user, info) => {
if (err) {
res.status(500).json({ status: err });
}
if (!user) {
res.status(404).json({ status: 'User not found' });
}
if (user) {
req.logIn(user, function(err) {
if (err) {
res.status(500).json({ status: 'Error while logging in' });
}
res.redirect('/');
});
}
})(req, res, next);
});
router.get('/user/logout', userUtils.loginRequired, (req, res, next) => {
req.logout();
res.status(200).json({ status: 'Logged out' });
});
database.js
const pgp = require('pg-promise')(initOptions);
const conn = pgp(connectionConfig);
module.exports = {
pgp,
conn
};
Any help would be much appreciated.