I use Passport-jwt Strategy for authentication in express project,
here is mt passport-jwt config in this directory: /config/passport.js
var JwtStrategy = require('passport-jwt')
.Strategy,
ExtractJwt = require('passport-jwt')
.ExtractJwt;
var User = require(__dirname + '/../models/user');
var config = require(__dirname+ '/database');
module.exports = function(passport) {
console.log("here: passport-jwt");
var opts = {}
opts.jwtFromRequest = ExtractJwt.fromAuthHeader();
opts.secretOrKey = config.secret;
passport.use(new JwtStrategy(opts, function(jwt_payload, done) {
User.findOne({
id: jwt_payload.id
}, function(err, user) {
if (err) {
return done(err, false);
}
if (user) {
done(null, user);
} else {
done(null, false);
}
});
}));
};
and in account route in /routes/account.js directory i call it this way:
var passport = require('passport');
require(__dirname + '/../config/passport')(passport);
router.post('/', passport.authenticate('jwt', {
session: false
}), function(req, res) { ... }
but the problem is passport function for authentication does not execute. and "here: passport-jwt" did not shown.
where is the problem?
Maybe you could try this:
router.get('/', function(req, res) {
passport.authenticate('jwt',
{
session: false
});
});
First of all in app.js, routes must be declared like this:
after adding these lines:
var passport = require('passport');
app.use(passport.initialize());
you should add these lines:
var account = require(__dirname + '/routes/account')(app, express, passport);
app.use('/account', account);
and in the route itself:
module.exports = function(app, express, passport) {
var router = express.Router();
router.post('/', function(req, res) {
passport.authenticate('jwt', function(err, user) {
if (err) {
res.sendStatus(406);
} else {
if (!user) {
res.sendStatus(400);
} else {...}
}
});
}
}
}
my mistake was that is put console.log("here: passport-jwt"); in the first line of module, but in fact passport.use(..) part executing every time!
and the last thing was the findOne part in passport config, passport.use(...) part, when you want to use native id in MongoDB, you should query _id instead of id!
So, the correct code is:
User.findOne({
_id: jwt_payload.id
}, function(err, user) {
...
});
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.
Hello I am quite new to nodejs and this is my first question. I want to add a user login to my express server. So I tried the passport.js example (express-4.x-local-example). Now I tried to store users in my sqlite database. I oriented on other question (node.js passport autentification with sqlite). But that didn't solved my problem. I don't want to create a live application with it, I just want to understand how it works. When I add a user to my database and try to login, I always get directed to /bad.
Here is the code I wrote:
The database.js
const sqlite3 = require("sqlite3");
const sqlite = require("sqlite");
const fs = require("fs").promises;
async function provideDatabase() {
const databaseExists = await fs
.access("./.data/database.db")
.catch(() => false);
const db = await sqlite.open({
filename: "./.data/database.db",
driver: sqlite3.Database
});
if (databaseExists === false) {
await db.exec(
"CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT, password TEXT, salt TEXT)"
);
}
return db;
}
module.exports = provideDatabase;
And here is my server.js
var express = require('express');
var passport = require('passport');
var Strategy = require('passport-local').Strategy;
const provideDatabase = require("./database");
const database = provideDatabase();
const LocalStrategy = require('passport-local').Strategy
const bodyParser = require("body-parser");
var app = express();
app.use(bodyParser.json());
var crypto = require('crypto');
function hashPassword(password, salt) {
var hash = crypto.createHash('sha256');
hash.update(password);
hash.update(salt);
return hash.digest('hex');
}
passport.use(new LocalStrategy(async function(username, password, done) {
const db = await database;
db.get('SELECT salt FROM users WHERE username = ?', username, function(err, row) {
if (!row) return done(null, false);
var hash = hashPassword(password, row.salt);
db.get('SELECT username, id FROM users WHERE username = ? AND password = ?', username, hash, function(err, row) {
if (!row) return done(null, false);
return done(null, row);
});
});
}));
passport.serializeUser(function(user, done) {
return done(null, user.id);
});
passport.deserializeUser(async function(id, done) {
const db = await database;
db.get('SELECT id, username FROM users WHERE id = ?', id, function(err, row) {
if (!row) return done(null, false);
return done(null, row);
});
});
app.post('/login', passport.authenticate('local', { successRedirect: '/good',
failureRedirect: '/bad' }));
// Configure view engine to render EJS templates.
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
// Define routes.
app.get('/',
function(req, res) {
res.render('home', { user: req.user });
});
app.get('/login',
function(req, res){
res.render('login');
});
app.get('/logout',
function(req, res){
req.logout();
res.redirect('/');
});
app.get('/profile',
require('connect-ensure-login').ensureLoggedIn(),
function(req, res){
res.render('profile', { user: req.user });
});
app.get("/user", async (request, response) => {
const db = await database;
const results = await db.all("SELECT * FROM users");
response.send(results);
});
app.post("/user", async (request, response) => {
const db = await database;
hashedPassword = hashPassword(request.body.password, request.body.salt)
const created = await db.run(
"INSERT INTO users (username, password, salt) VALUES(?,?,?)",
request.body.username,
hashedPassword,
request.body.salt
);
const user = await db.get("SELECT * FROM users WHERE Id = ?", [
created.lastID
]);
response.status(201).send(user);
});
app.listen(3000);
I am grabbing my code from a project in production. Although it is not using SQLite, the concept should be the same.
I use bcrypt instead of implementing my own crypto. I suggest you do the same to avoid error and security issues in case of misapplication.
I also put the passport logic in a separate file to make the code looks cleaner and avoid confusion.
// Dependencies
const passport = require('passport');
const { Strategy: LocalStrategy, } = require('passport-local');
const bcrypt = require('bcrypt');
// Load model for User_DB
const { User_DB, } = require('../dataBase/dbConnection');
// Winston Logger
const passLog = require('../system/log').get('passportLog');
// Session
// Take in user id => keep the session data small
passport.serializeUser((id, done) => {
done(null, id);
});
// Deserialize when needed by querying the DB for full user details
passport.deserializeUser(async (id, done) => {
try {
const user = await User_DB.findById(id);
done(null, user);
} catch (err) {
passLog.error(`Error Deserializing User: ${id}: ${err}`);
}
});
// Export the passport module
module.exports = (passport) => {
passport.use(new LocalStrategy({ usernameField: 'email', }, async (email, password, done) => {
try {
// Lookup the user
const userData = await User_DB.findOne({ email: email, }, {
password: 1, }); // Return the password hash only instead of the whole user object
// If the user does not exist
if (!userData) {
return done(null, false);
}
// Hash the password and compare it to the hash in the database
const passMatch = await bcrypt.compare(password, userData.password);
// If the password hash does not match
if (!passMatch) {
return done(null, false);
}
// Otherwise return the user id
return done(null, userData.id);
} catch (err) {
passLog.error(`Login Error: ${err}`);
}
}));
};
These options for passport seems to malfunction a lot or exhibit weird behaviors, so I suggest you handle the redirection logic like in my controller.
{ successRedirect: '/good',
failureRedirect: '/bad' }
Login controller logic:
(I am omitting the code here for session storage and made some modifications, but this code should work for what you need)
const login = (req, res, next) => {
//Using passport-local
passport.authenticate('local', async (err, user) => {
//If user object does not exist => login failed
if (!user) { return res.redirect('/unauthorized'); }
//If all good, log the dude in
req.logIn(user, (err) => {
if (err) { return res.status(401).json({ msg: 'Login Error', }); }
// Send response to the frontend
return res.redirect('/good');
});
});
})(req, res, next);
};
The actual route:
// Import the controller
const {login} = require('../controllers/auth');
// Use it in the route
router.post('/auth/login', login);
I'm able to differentiate the two routes and
authenticate the first route(student). When i call the second route(teacher) to be authenticated, a empty JSON file is returned.
In users.js 'passportStudent.authenticate' works and returns a JSON file with user information as intended, But i'm not sure why 'passportTeacher.authenticate' returns a empty JSON file.
Here's My Code:
users.js:
const passportStudent = require('passport');
const passportTeacher = require('passport');
/*Some code here*/
router.get('/profile/student', passportStudent.authenticate('student', {session: false}), (req, res, next) =>{
res.json({user : req.user});
});
router.get('/profile/teacher', passportTeacher.authenticate('teacher', {session: false}), (req, res, next) =>{
res.json({teacher : req.teacher});
});
passport.js:
const JwtStrategy = require('passport-jwt').Strategy;
const ExtractJwt = require('passport-jwt').ExtractJwt;
const User = require('../models/user');
const Teacher = require('../models/teacher');
const config = require('../config/database');
module.exports = function(passport){
let opts = {};
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderWithScheme('jwt');
opts.secretOrKey = config.secret;
passport.use('student', new JwtStrategy(opts, (jwt_payload, done) => {
console.log(jwt_payload);
User.getUserById(jwt_payload.data._id, (err, user) => {
if(err){
return done(err, false);
}
if(user){
return done(null, user);
}
else{
return done(null, false);
}
});
}));
passport.use('teacher', new JwtStrategy(opts, (jwt_payload2, done) => {
console.log(jwt_payload2);
Teacher.getTeacherById(jwt_payload2.data._id, (err, teacher) => {
if(err){
return done(err, false);
}
if(teacher){
return done(null, teacher);
}
else{
return done(null, false);
}
});
}));
}
app.js code for Passport:
const passportStudent = require('passport');
const passportTeacher = require('passport');
/*Some code here*/
app.use(passportStudent.initialize());
app.use(passportStudent.session());
app.use(passportTeacher.initialize());
app.use(passportTeacher.session());
require('./config/passport')(passportStudent);
require('./config/passport')(passportTeacher);
How can I authenticate either on desired routes? Or should I go for local strategy for student and jwt for teacher?
Thanks for your help.
In both cases, the passport store the signed users information in req.user.
So you need to update the teacher router to
router.get('/profile/teacher', passportTeacher.authenticate('teacher', {session: false}), (req, res, next) =>{
res.json({teacher : req.user});
});
Here I change
res.json({teacher : req.teacher});
To
res.json({teacher : req.user});
When I use 'passport.authenticate('jwt', { session: false })' in a route, it returns 'Unauthorized'. The passport middleware seems not working. I don't know what is wrong! In the header I'm using the 'Authorization: JWT MY_TOKEN'.
app.js file
// ...code
const app = express();
app.use(bodyParser.json());
app.use(passport.initialize());
require('./config/passport')(passport); // passport middleware file
app.use('/api/auth', auth); // routes
app.use('/api/lists', lists); // routes
.
Passport middleware file:
const { Strategy, ExtractJwt } = require('passport-jwt');
const User = require('../models/User');
const keys = require('./keys');
module.exports = (passport) => {
const opts = {};
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
opts.secretOrKey = 'MYSECRETKEY';
// HERE IS EXECUTED! <<<<<<<<
passport.use(new Strategy(opts, function(jwt_payload, done) {
// HERE IS NOT BEING EXECUTED! <<<<<<<<
User.findOne({_id: jwt_payload.id}, function(err, user) {
if (err) {
return done(err, false);
}
if (user) {
return done(null, user);
} else {
return done(null, false);
}
});
}));
}
.
Login route (creating the token):
router.post('/login', (req, res, next) => {
const { email, } = req.body;
User.findOne({ email: email }, { password: 0 }, (errQuery, resUser) => {
const token = jwt.sign({_id: resUser.id}, 'MYSECRETKEY');
const responseData = {
status: true,
token: `JWT ${token}`
};
res.send(responseData);
});
});
The issue is this line:
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
Because you're calling that configuration option, your header needs to look like:
Authorization: Bearer MY_TOKEN
The code
app.js:
var express = require('express');
var session = require('express-session');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var mongoStore = require('connect-mongo')(session);
var mongoose = require('mongoose');
var passport = require('passport');
var config = require('./config');
var routes = require('./routes');
var mongodb = mongoose.connect(config.mongodb);
var app = express();
// view engine setup
app.set('views', config.root + '/views');
app.set('view engine', 'jade');
app.engine('html', require('ejs').renderFile);
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: false
}));
app.use(cookieParser());
app.use(express.static(config.root + '/public'));
app.use(session({
name: 'myCookie',
secret: 'tehSecret',
resave: true,
saveUninitialized: true,
unset: 'destroy',
store: new mongoStore({
db: mongodb.connection.db,
collection: 'sessions'
})
}));
app.use(passport.initialize());
app.use(passport.session());
app.use('/', routes);
app.set('port', config.port);
var server = app.listen(app.get('port'), function() {
if (config.debug) {
debug('Express server listening on port ' + server.address().port);
}
});
routes.js:
var express = require('express');
var router = express.Router();
var config = require('../config');
var userController = require('../controllers/user');
var authController = require('../controllers/auth');
router.get('/', function(req, res) {
res.render('index', {
title: config.app.name
});
});
router.route('/users')
.post(userController.postUsers)
.get(authController.isAuthenticated, userController.getUsers);
router.get('/signout', userController.signout);
module.exports = router;
models/user.js:
var mongoose = require('mongoose');
var bcrypt = require('bcrypt-nodejs');
var UserSchema = new mongoose.Schema({
username: {
type: String,
unique: true,
required: true
},
password: {
type: String,
required: true
}
});
// Execute before each user.save() call
UserSchema.pre('save', function(callback) {
var user = this;
// Break out if the password hasn't changed
if (!user.isModified('password')) return callback();
// Password changed so we need to hash it
bcrypt.genSalt(5, function(err, salt) {
if (err) return callback(err);
bcrypt.hash(user.password, salt, null, function(err, hash) {
if (err) return callback(err);
user.password = hash;
callback();
});
});
});
UserSchema.methods.verifyPassword = function(password, cb) {
bcrypt.compare(password, this.password, function(err, isMatch) {
if (err) return cb(err);
cb(null, isMatch);
});
};
// Export the Mongoose model
module.exports = mongoose.model('User', UserSchema);
controllers/user.js:
var config = require('../config');
var User = require('../models/user');
exports.postUsers = function(req, res) {
if (config.debug)
console.log("user.postUsers()");
var user = new User({
username: req.body.username,
password: req.body.password
});
user.save(function(err) {
if (err)
return res.send(err);
if (config.debug)
console.log("saved");
res.json({
message: 'New user created!'
});
});
};
exports.getUsers = function(req, res) {
if (config.debug)
console.log("user.getUsers()");
User.find(function(err, users) {
if (err)
return res.send(err);
if (config.debug)
console.log("users", users);
res.json(users);
});
};
exports.signout = function(req, res) {
if (config.debug)
console.log("user.signout()");
res.clearCookie('myCookie');
req.session.destroy(function(err) {
req.logout();
res.redirect('/');
});
};
controllers/auth.js:
var passport = require('passport');
var BasicStrategy = require('passport-http').BasicStrategy;
var config = require('../config');
var User = require('../models/user');
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
done(err, user);
});
});
passport.use(new BasicStrategy(
function(username, password, done) {
User.findOne({
username: username
}, function(err, user) {
if (err) {
return done(err);
}
// No user found with that username
if (!user) {
return done(null, false);
}
// Make sure the password is correct
user.verifyPassword(password, function(err, isMatch) {
if (err) {
return done(err);
}
// Password did not match
if (!isMatch) {
return done(null, false);
}
// Success
return done(null, user);
});
});
}
));
exports.isAuthenticated = passport.authenticate('basic', {
session: false
});
The problem
/signout route does not end the current session. In the req.session.destroy callback the req.session is undefined, yet a new GET request to /users acts like the session is valid.
Can someone help clear this problem out?
If, like me, you came here as a result of question title rather than full details- the answer is req.session.destroy(). I think the logout function is particular to passport.js and will not work if you are using standard express-session.
Solution
controllers/user.js:
exports.signout = function(req, res) {
if (config.debug)
console.log("user.signout()");
req.logout();
res.send(401);
};
Btw. don't mind the session(s) still being in DB immediately after the logout. Mongod checks and clears those out after 60 s.
in sign out api without using req.session.destroy() try req.logout();. I hope it will work.
In my case the server-side code was fine. It was the client-side code where I wasn't including the withCredentials parameter when making the http request.
Below is the correct working code.
// server side (nodejs)
authRouter.post("/logout",
passport.session(),
checkAuthenticationHandler,
async (req, res, next) => {
req.logOut(err => {
if (err) next(err)
res.status(http.statusCodes.NO_CONTENT).end()
})
})
// client side (reactjs)
export const logout = async () => {
const _response = await axios({
method: 'post',
url: `${authApi}/auth/logout`,
withCredentials: true
})
}