I'm struggling with understanding how authentication with Passport works. I'm using Express.js as my routing management package and I have 3 usable routes and one router :
routes
├── auth.endpoints.js
├── concept.endpoints.js
├── routes.js
└── user.endpoints.js
routes.js is my API gateway, handling traffic to the different routes and it looks like this :
const { isConnected } = require("../middleware/is.connected"); //middleware that checks whether req.user exists or not
module.exports = function (app, passport) {
const userRouter = require("./user.endpoints");
const authRouter = require("./auth.endpoints")(passport);
const conceptRouter = require("./concept.endpoints");
const logger = require("../log/winston.logger");
const logLevels = require("../log/log.levels.json").levels;
app.use(function (req, res, next) {
logger(req.method + " " + req.url, logLevels.http);
next();
});
app.use("/auth", authRouter);
app.use(isConnected);
app.use("/api/user", userRouter);
app.use("/api/concept", conceptRouter);
};
The issue is in the auth route, more precisely, the signup endpoint :
// #ts-nocheck
const express = require("express");
const router = express.Router();
const { isConnected } = require("../middleware/is.connected");
const ErrorMiddleware = require("../middleware/error.middleware");
const { isAValidInstance } = require("../utils/schema.validator");
const userSchema = require("../database/schemas/user.schema.json");
const User = require("../database/models/user.model");
const logger = require("../log/winston.logger");
const SHA256 = require("js-sha256");
const logLevels = require("../log/log.levels.json").levels;
const ResponseError = require("../responses/response.error");
const ResponseSuccess = require("../responses/response.success");
module.exports = function (passport) {
router.get(
"/logout",
isConnected,
ErrorMiddleware(function (req, res, next) {
req.logout();
res.clearCookie("connect.sid", {
path: "/",
});
// eslint-disable-next-line node/handle-callback-err
req.session.destroy(function (err) {
ResponseSuccess.success(res);
});
})
);
router.post(
"/login",
passport.authenticate("local", {
successRedirect: "/auth/me",
failureRedirect: "/login",
})
);
router.get(
"/me",
isConnected,
ErrorMiddleware((req, res, next) => {
ResponseSuccess.success(res, req.user);
})
);
router.post(
"/signup",
ErrorMiddleware(async (req, res, next) => {
if (req.user !== undefined) {
res.redirect("/me");
return;
}
const user = req.body;
if (!isAValidInstance(user, userSchema)) {
logger("POST /auth/signup", logLevels.warn, "Invalid body");
ResponseError.badRequest(res);
return;
}
user.password = SHA256(user.password);
const createdUser = await User.createUser(user);
if (createdUser !== null) {
ResponseSuccess.success(res, createdUser);
} else {
ResponseError.unauthorized(res);
}
})
);
return router;
};
The problem is that after I sign up, I still have to manually login (at least via the backend and Postman) in order to be considered authenticated.
Is there something wrong with the way I'm doing things?
FYI, this is my current passport configuration :
// #ts-nocheck
const passport = require("passport");
const User = require("../database/models/user.model");
const SHA256 = require("js-sha256");
const LocalStrategy = require("passport-local").Strategy;
const session = require("express-session");
module.exports = function (app) {
passport.use(
new LocalStrategy(
{
usernameField: "email",
passwordField: "password",
},
async (email, password, done) => {
const user = await User.findUser(email, SHA256(password));
if (user == null) {
return done(null, false);
}
return done(null, user.user_id);
}
)
);
passport.serializeUser((userId, done) => {
return done(null, userId);
});
passport.deserializeUser(async function (userId, done) {
const user = await User.getUserByUserId(userId);
return done(null, user);
});
app.use(
session({ secret: "anything", resave: false, saveUninitialized: false })
);
app.use(passport.initialize());
app.use(passport.session());
return passport;
};
I feel like the serialisation isn't working correctly.
Many thanks in advance for your help!
Related
So I am following a tutorial on how to use JSON tokens and I am getting an error, it was working fine using sessions but I can't figure out why I am having trouble, it is the exact code
this is my authenticate.js file:
const passport = require("passport");
const LocalStrategy = require("passport-local").Strategy;
const User = require("./models/user");
const JwtStrategy = require("passport-jwt").Strategy;
const ExtractJwt = require("passport-jwt").ExtractJwt;
const jwt = require("jsonwebtoken"); // used to create, sign, and verify tokens
const config = require("./config.js");
exports.local = passport.use(new LocalStrategy(User.authenticate()));
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());
exports.getToken = function (user) {
return jwt.sign(user, config.secretKey, { expiresIn: 3600 });
}; // config.secretKey is a string of random numbers
const opts = {};
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
opts.secretOrKey = config.secretKey;
exports.jwtPassport = passport.use(
new JwtStrategy(opts, (jwt_payload, done) => {
console.log("JWT payload:", jwt_payload);
User.findOne({ _id: jwt_payload._id }, (err, user) => {
if (err) {
return done(err, false);
} else if (user) {
return done(null, user);
} else {
return done(null, false);
}
});
})
);
exports.verifyUser = passport.authenticate("jwt", { session: false });
This is my app.js file (the main file):
const createError = require("http-errors");
const express = require("express");
const path = require("path");
const logger = require("morgan");
const config = require("./config");
const indexRouter = require("./routes/index");
const usersRouter = require("./routes/users");
const mongoose = require("mongoose");
const passport = require("passport");
const url = config.mongoUrl;
const connect = mongoose.connect(url, {
useCreateIndex: true,
useFindAndModify: false,
useNewUrlParser: true,
useUnifiedTopology: true,
});
connect.then(
() => console.log("Connected correctly to server"),
(err) => console.log(err)
);
const app = express();
// view engine setup
app.set("views", path.join(__dirname, "views"));
app.set("view engine", "jade");
app.use(logger("dev"));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
// app.use(cookieParser("12345-67890-09876-54321"));
app.use(passport.initialize());
app.use("/", indexRouter);
app.use("/users", usersRouter);
app.use(express.static(path.join(__dirname, "public")));
// catch 404 and forward to error handler
app.use(function (req, res, next) {
next(createError(404));
});
// error handler
app.use(function (err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get("env") === "development" ? err : {};
// render the error page
res.status(err.status || 500);
res.render("error");
});
module.exports = app;
this is the routes/users.js file (I believe the problem is here because I can sign-up (create new users) but I can't login with the same users)
const express = require("express");
const User = require("../models/user");
const passport = require("passport");
const authenticate = require("../authenticate");
const router = express.Router();
/* GET users listing. */
router.get(
"/",
function (req, res, next) {
res.send('send users')
}
);
router.post("/signup", (req, res) => {
User.register(
new User({ username: req.body.username }),
req.body.password,
(err, user) => {
if (err) {
res.statusCode = 500;
res.setHeader("Content-Type", "application/json");
res.json({ err: err });
} else {
if (req.body.firstname) {
user.firstname = req.body.firstname;
}
if (req.body.lastname) {
user.lastname = req.body.lastname;
}
user.save((err) => {
if (err) {
res.statusCode = 500;
res.setHeader("Content-Type", "application/json");
res.json({ err: err });
return;
}
passport.authenticate("local")(req, res, () => {
res.statusCode = 200;
res.setHeader("Content-Type", "application/json");
res.json({
success: true,
status: "Registration Successful!",
});
});
});
}
}
);
});
// I tried to add a console.log inside of the .post() route but it never reach it
router.post(
"/login",
passport.authenticate("local"),
(req, res) => {
const token = authenticate.getToken({ _id: req.user._id });
res.statusCode = 200;
res.setHeader("Content-Type", "application/json");
res.json({
success: true,
token: token,
status: "You are successfully logged in!",
});
}
);
router.get("/logout", (req, res, next) => {
if (req.session) {
req.session.destroy();
res.clearCookie("session-id");
res.redirect("/");
} else {
const err = new Error("You are not logged in!");
err.status = 401;
return next(err);
}
});
module.exports = router;
Basically, every time that i go to localhost:3000/users/login and send a POST request with the username and password, it tells me that I need to use express-session but I am trying to use tokens instead of session
The problem is caused when passport.authenticate('local') is called in routes/users.js file. It is a middleware that automatically calls req.login function in case correct username and password is provided.
The req.login() in turn, implements sessions in order to serialise the user in the session.
You can solve the issue by adding another parameter to passport.authenticate() as passport.authenticate('local', {session: false}. This ensures sessions are not implemented in the 'local' strategy and subsequently login can be performed.
Thanks, it helped me. You have to remove app.use(passort.session) from app.js and do
router.post('/login', passport.authenticate('local', { session: false }), (req, res) => {
var token = authenticate.getToken({ _id: req.user._id });
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.json({ success: true, token: token, status: 'You are successfully logged in!' });
});
I'm getting error "TypeError: next is not a function" while trying to authenticate dashboard route in nodejs.
I am trying to make and CRUD app with node and mongoDB suing these modules express ejs mongoose bcryptjs passport passport-local.
Getting this error when I submit login form.
I am new in nodejs, Please help me
Thanks in advance.
auth/protect.js file
const protectRoute = (req, res, next) =>{
if (req.isAuthenticated()) {
return next();
}
console.log('Please log in to continue');
res.redirect('/login');
}
const allowIf = (req, res, next) =>{
if (!req.isAuthenticated()) {
return next();
}
res.redirect('/dashboard');
}
module.exports = {
protectRoute,
allowIf,
};
routes/login.js file
const express = require("express");
const {
registerView,
loginView,
registerUser,
loginUser,
} = require("../controllers/loginController");
const { dashboardView } = require("../controllers/dashboardController");
const { protectRoute } = require("../auth/protect");
const router = express.Router();
router.get("/register", registerView);
router.get("/login", loginView);
router.get("/", loginView);
//Dashboard
router.get("/dashboard", protectRoute, dashboardView);
router.post("/register", registerUser);
router.post("/login", loginUser);
module.exports = router;
server.js file
const express = require("express");
const cors = require("cors");
const app = express();
const mongoose = require('mongoose');
const session = require('express-session');
const passport = require("passport");
const { loginCheck } = require("./auth/passport");
var corsOptions = {
origin: "http://localhost:8081"
};
app.use(cors(corsOptions));
const db = require("./models");
db.mongoose
.connect(db.url, {
useNewUrlParser: true,
useUnifiedTopology: true
})
.then(() => {
console.log("Connected to the database!");
})
.catch(err => {
console.log("Cannot connect to the database!", err);
process.exit();
});
app.set('view engine', 'ejs');
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(session({
secret:'oneboy',
saveUninitialized: true,
resave: true
}));
app.use(passport.initialize());
app.use(passport.session());
// simple route
app.use('/', require('./routes/login'));
const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}.`);
});
I have missed to call a function loginCheck(passport); in my server.js file that's why I was getting error during login form submission.
server.js starting code
const express = require("express");
const cors = require("cors");
const app = express();
const mongoose = require('mongoose');
const session = require('express-session');
const passport = require("passport");
//var LocalStrategy = require('passport-local').Strategy;
const { loginCheck } = require("./auth/passport");
loginCheck(passport);
........................................
I was added this function in auth/passport.js file
passport.js
//js
const bcrypt = require("bcryptjs");
LocalStrategy = require("passport-local").Strategy;
//Load model
const User = require("../models/User");
const loginCheck = passport => {
passport.use(
new LocalStrategy({ usernameField: "email" }, (email, password, done) => {
//Check customer
User.findOne({ email: email })
.then((user) => {
if (!user) {
console.log("wrong email");
return done();
}
//Match Password
bcrypt.compare(password, user.password, (error, isMatch) => {
if (error) throw error;
if (isMatch) {
return done(null, user);
} else {
console.log("Wrong password");
return done();
}
});
})
.catch((error) => console.log(error));
})
);
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser((id, done) => {
User.findById(id, (error, user) => {
done(error, user);
});
});
};
module.exports = {
loginCheck,
};
If you are trying to use next() in normal function then it will give an error
const allowIf = (req, res, next) =>{
return next(); // throw an error - TypeError: next is not a function
}
allowIf();
So use next() as a Callback argument to the middleware function. It will work fine in this case. Try this:
const protectRoute = (req, res, next) =>{
console.log('protectRoute');
return next();
}
app.get('/', protectRoute);
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'm trying to make a register/login with node, express and mongoose. I want to register and login users and if they aren't logged in they can't retrieve data from the api.
To make the login and register i have been watching this tutorial: Link it isn't as addecuate for my needs because half of the efforts and code goes into the ejs but i have been okay until the passport login.
I have created my local strategy in config/passport.js:
const LocalStrategy = require("passport-local").Strategy;
const bcrypt = require("bcryptjs");
const User = require("../models/user");
module.exports = function (passport) {
passport.use(
new LocalStrategy({ usernameField: "email" }, (email, password, done) => {
User.findOne({ email: email })
.then((user) => {
if (!user) {
return done(null, false, {
message: "That email is not registered",
});
}
bcrypt.compare(password, user.password, (err, isMatch) => {
if (err) console.log(err);
if (isMatch) {
return done(null, user);
} else {
done(null, false, { message: "Incorrect Password" });
}
});
})
.catch((err) => console.log(err));
})
);
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser((id, done) => {
User.findById(id, (err, user) => {
done(err, user);
});
});
};
My app.js looks like this:
const express = require("express");
const cors = require("cors");
const usersRouter = require("./routes/users");
const passport = require("passport");
const session = require("express-session");
require("dotenv").config();
const app = express();
require("./config/passport")(passport);
const connectDB = require("./db/connect");
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(
session({
secret: "*****",
resave: true,
saveUninitialized: true,
})
);
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 users router looks like this:
const express = require("express");
const router = express.Router();
const { postRegister } = require("../controllers/register");
router.route("/register").post(postRegister);
const { postLogin } = require("../controllers/login");
router.route("/login").post(postLogin);
module.exports = router;
The register works perfectly, but the login controller doesn't. Each time i post to that endpoint instead of getting a succesMessage or failureMessage i get the page not found router that is called at the end of app.js, i checked if the router calls the login function via a console log and it does. Here is the code for the login controller:
const passport = require("passport");
const postLogin = (req, res, next) => {
console.log("The function has been triggered");
passport.authenticate("local", {
successMessage: "Login authenticated",
failureMessage: "Login failed",
})(req, res, next);
};
module.exports = {
postLogin,
};
Why don't I get the responses from the login controller or my local strategy and instead i get page not found when the Login controller actually works?
*Please don't ignore the question looking at its length. It might seem long because I've attached the code. The actual problem statement is really small.
I've tried integrating Passport, Koa & MongoDB from the last two days. I've been failing and am unable to get any references regarding the problem I've been facing. My primary concern is to just get the login module working.
My problem is that the function to handle post request for login in my index routes, associated with koa-passport is being called and ctx.body is also being sent as a response but successRedirect and failureRedirect aren't working. Down below are the files that I'm using.
In the auth.js, I have console logged user inside the new LocalStrategy, right after var user = line. Nothing is logged to the console.
auth.js
const passport = require('koa-passport')
const LocalStretegy = require('passport-local').Strategy
const mongoose = require('mongoose')
const User = mongoose.model('User', mongoose.Schema({}, { strict: false }))
function *getUser(name) {
let user = yield User.find({username: name})
return user
}
passport.serializeUser( (user, done) => {
done(null, user.id)
})
passport.deserializeUser( async (id, done) => {
done(null, user)
})
passport.use(new LocalStretegy( (username, password, done) => {
co(function*() {
try {
var user = yield getUser(username)
if (user.username === username && user.password === password) {
return user
} else {
return null
}
} catch(e) {
return null
}
}).then((user) => {
done(null, user)
})
}))
module.exports = passport
/routes/index.js
const router = require('koa-router')()
const sendfile = require('koa-sendfile')
const passport = require('koa-passport')
const mongoose = require('mongoose')
const Counter = mongoose.model('Counter', mongoose.Schema({}, { strict: false }))
router.post('/custom', (ctx, next) => {
return passport.authenticate('local', function(user, info, status) {
if (user === false) {
ctx.status = 401
ctx.body = { success: false }
ctx.body = { success: true }
} else {
return ctx.login(user)
}
})(ctx, next)
})
router.post('/login', (ctx) => {
passport.authenticate('local', {
successRedirect: '/dash-board',
failureRedirect: '/login'
})
ctx.body={status: 'okay'}
})
router.get('/logout', (ctx) => {
ctx.logout()
ctx.redirect('/')
})
module.exports = router
app.js
const Koa = require('koa')
const router = require('koa-router')()
const app = new Koa()
app.proxy = true
//Routes
const users = require('./routes/users')
const index = require('./routes/index')
//Error handler
const onerror = require('koa-onerror')
onerror(app)
//MongoDB connection
const mongoose = require('mongoose')
mongoose.Promise = global.Promise
try{
mongoose.connect('mongodb://<user>:<pass>#<something>.mlab.com:<number>/<someDB>?poolSize=10&retries=5')
} catch(err) {
console.log(err)
}
// Sessions
const convert = require('koa-convert')
const session = require('koa-generic-session')
const MongoStore = require('koa-generic-session-mongo')
app.keys = ['key1']
app.use(convert(session({
store: new MongoStore()
})))
//body parser
const bodyparser = require('koa-bodyparser')
app.use(bodyparser({
enableTypes:['json', 'form', 'text']
}))
//passport
require('./auth')
const passport = require('koa-passport')
app.use(passport.initialize())
app.use(passport.session())
app.use(index.routes(), index.allowedMethods(), users.prefix('/index'))
app.use((ctx, next) => {
if(ctx.isAuthenticated()) {
return next()
} else {
ctx.redirect('/')
}
})
app.use((ctx, next) => {
return next().catch(function (err) {
ctx.status = err.status || 500;
if (err.expose) {
ctx.body = err.message;
}
})
})
module.exports = app
I am unable to deduce anything from here on. I've searched extensively and changed the code to work until here.
Maybe it has something to do with this code in which user does not seem to be declared.
passport.deserializeUser( async (id, done) => {
done(null, user)
})