I'm having issues setting up passport authentication with my server. I've used passportjs before but with mongodb. I'm currently trying to setup my local strategy with postgressql but have had no luck. When going to a login POST route with passport.authenticate(), I'm not getting a cookie sent back to me. I'm not sure if I setup my server correctly with passportJS and with my postgres database hosted via Heroku.
require('dotenv').config(); //In order to gain access to our .env file
//process.env.YELP_API_KEY
const express = require("express");
const app = express();
const bodyParser = require("body-parser");
app.use(bodyParser.json()); //This is going to allow us to access data stored in 'body' when making a post or put request
app.use(bodyParser.urlencoded({extended: true}));
const { Pool } = require('pg');
const fs = require("fs"); //This is needed to convert sql files into a string
let port = process.env.PORT || 5000;
//session-related libraries
const session = require("express-session");
const passport = require("passport"); //This is used for authentication
const LocalStrategy = require("passport-local").Strategy;
const bcrypt = require("bcrypt");
//Setting up our session
app.use(session({
secret: process.env.SECRET,
resave: false,
saveUninitialized: false
}));
//Connection to database
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
ssl: true
}); //This is used to connect to our remote postegres database hosted via Heroku
//initializing our session
app.use(passport.initialize());
app.use(passport.session()); //Telling our app to use passport for dealing with our sessions
//setting up our local strategy
passport.use('local', new LocalStrategy({passReqToCallBack: true},( username, password, cb )=> {
console.log("this is being executed");
pool.query("SELECT id, username, password from users where username=$1", [username], (err, result) => {
if(err){
return cb(err);
}
if(result.rows.length > 0){
const first = result.rows[0];
bcrypt.compare(password, first.password, (err, res) => {
if(res){
cb(null, {
id: first.id,
user: first.username
})
}
else {
cb(null, false);
}
})
}
else {
cb(null, false);
}
})
}));
passport.serializeUser(function(user, done){
console.log("serialize user is executing")
done(null, user.id);
})
passport.deserializeUser(function(id, done){
pool.query('SELECT id, username FROM users WHERE id = $1', [parseInt(id, 10)], (err, results) => {
if(err) {
return done(err)
}
done(null, results.rows[0])
});
});
app.post("/api/login", (req, res) => {
passport.authenticate('local', function(err, user, info){
console.log(user);
});
})
app.listen(port, function(){
console.log("Your app is running on port " + port);
});
Expected Results: user should be able to login with the post route "/api/login" but passport.authenticate is not working? Passport local strategy should also be correctly setup.
In your route app.post("/api/login", .... the passport.authenticate needs an access to the req and res.
There's more than one way to make it work.
app.post("/api/login", (req, res) => {
passport.authenticate('local', function(err, user, info){
console.log(user);
// make sure to respond to the request
res.send(user);
})(req, res); // <= pass req and res to the passport
})
// or
// use it as a middleware
app.post("/api/login", passport.authenticate('local'), (req, res) => {
console.log(req.user);
// make sure to respond to the request
res.send(req.user);
})
Related
I have a React client setup at localhost:3000 and a node.js server at localhost:5000
I'm trying a simple auth flow in which the client tries to authenticate with the server using Passport.js Google OAuth2.0 and staying authenticated using express-sessions with a MongoDB store.
I believe the reason I'm finding req.user is undefined is because of my lack of understanding of how the auth flow is supposed to work versus any issues with the actual code.
I'm initiating the auth flow through the following code in the react client:
<Button href="http://localhost:5000/auth/google">
Login using Google
</Button>
The following is my auth.js file:
const express = require("express");
const passport = require("passport");
const router = express.Router();
// #desc Auth with Google
// #route GET /auth/google
router.get("/google", passport.authenticate("google", { scope: ["profile"] }));
// #desc Google auth callback
// #route GET /auth/google/callback
router.get(
"/google/callback",
passport.authenticate("google", {
failureRedirect: "/",
successRedirect: "http://localhost:3000/dashboard",
})
);
// #desc Logout user
// #route /auth/logout
router.get("/logout", (req, res) => {
req.logout();
res.redirect("/");
});
module.exports = router;
The following is my Google Strategy configuration:
const GoogleStrategy = require("passport-google-oauth20").Strategy;
const mongoose = require("mongoose");
const User = require("../models/User");
module.exports = function (passport) {
passport.use(
new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: "http://localhost:5000/auth/google/callback",
},
async (accessToken, refreshToken, profile, done) => {
const newUser = {
googleId: profile.id,
displayName: profile.displayName,
firstName: profile.name.givenName,
lastName: profile.name.familyName,
image: profile.photos[0].value,
};
try {
let user = await User.findOne({ googleId: profile.id });
if (user) {
done(null, user);
} else {
user = await User.create(newUser);
done(null, user);
}
} catch (err) {
console.error(err);
}
}
)
);
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser((id, done) => {
User.findById(id, (err, user) => done(err, user));
});
};
The following code is my index.js which brings everything together:
const express = require("express");
const cors = require("cors");
const bodyParser = require("body-parser");
const mongoose = require("mongoose");
const connectDB = require("./config/db");
const morgan = require("morgan");
const passport = require("passport");
const session = require("express-session");
const MongoStore = require("connect-mongo")(session);
// Dotenv config
const dotenv = require("dotenv").config({
path: "./config/config.env",
});
// Passport config
require("./config/passport")(passport);
// MongoDB config
connectDB();
const app = express();
const PORT = process.env.PORT;
// Middleware
app.use(cors());
app.use(bodyParser.json());
app.use(morgan("dev"));
// Sessions
app.use(
session({
secret: "stackoverflow",
resave: false,
saveUninitialized: false,
store: new MongoStore({ mongooseConnection: mongoose.connection }),
})
);
// Passport middleware
app.use(passport.initialize());
app.use(passport.session());
app.use("/posts", require("./routes/posts"));
app.use("/auth", require("./routes/auth"));
app.listen(PORT, () => console.log(`Server listening # port ${PORT}`));
I am fetching posts from the DB through one of the routes after user login:
...
// #desc Get all posts
// #route GET /posts
router.get("/", (req, res) => {
const posts = Post.find(function (error, posts) {
if (error) return console.error(error);
console.log(req.user) // <------ is undefined
res.json(posts);
});
});
I'm assuming that after the user is being redirected to the dashboard and then sending a request to the route to get all posts, the user is not authenticated? Although I redirected the user towards this route after authenticating him?
The goal is definitely not to fetch the user at the /posts route but create a separate /user route that returns the user to the client, but that also results in req.user being undefined.
Finally got it to work.
I had to edit two things,
First (Server Side):
I had to setup CORS to allow cookies to pass through as such:
app.use(
cors({
origin: "http://localhost:3000", // allow to server to accept request from different origin
methods: "GET,HEAD,PUT,PATCH,POST,DELETE",
credentials: true, // allow session cookie from browser to pass through
})
);
Second (Client Side):
I had to let Axios know that it has to send the cookie alongside the request as such:
axios
.get("http://localhost:5000/auth/user", { withCredentials: true })
.then(console.log)
.catch(console.error);
where the /auth/user route is defined as such:
router.get("/user", (req, res) => {
if (req.user) {
res.json(req.user);
}
});
These mistakes could have been avoided had I had a better understanding of the entire authentication process, of which now I do.
We live and we learn.
At first haidousm's answer did not work for me, what I did was I added { withCredentials: true } to the post request for my login as well.
I have a strange problem with my NodeJs - Express server which serves as a back-end for my mobile application.
The thing is that i send post requests to some endpoints like checkmail, checkusername from front-end using axios and it works, but the problem is it doesn't work for any other middleware function. I literally copied the same checkmail and just used different route and I get status 404 while with /checkmail it works!
Also, the /login does not work, im using express. Router in there.
Here is my app.js code:
const express = require("express");
const mongoose = require("mongoose");
const bodyParser = require("body-parser");
var cors = require("cors");
const User = require("./models/user");
var AuthController = require('./auth/authController');
const app = express();
let server = require("http").Server(app);
app.use(cors());
app.use(
bodyParser.urlencoded({
extended: true
})
);
app.use(bodyParser.json());
//Check if e-mail is aready in use, return "success" if not
app.use("/signup", (req, res) => {
User.findOne({
email: req.body.email
},
function (err, user) {
if (user) {
res.send("error");
} else {
res.send("success");
}
}
);
});
//Check if e-mail is aready in use, return "success" if not
app.use("/checkmail", (req, res) => {
User.findOne({
email: req.body.email
},
function (err, user) {
if (user) {
res.send("error");
} else {
res.send("success");
}
}
);
});
app.use('/login', AuthController);
const port = process.env.PORT || 8500;
server.listen(port, () => { });
Middleware should have third parameter next.
app.use("/checkmail", (req,res,next)=>{
//do something
})
You must have third parameter in middleware, it's callback to tell application go to next route.
app.use('/signup', (req, res, next) => { // Normal middleware, it must have 3 parameters
User.findOne({
email: req.body.email
},
function (err, user) {
if (user) {
next(new Error('Some thing wrong')); // Failed, we will go to error-handling middleware functions
} else {
next(); // Success and we will go to next route
}
}
);
});
app.get('/signup', (req, res, next) => { // This is "next route"
res.send('Yay, welcome to signup')
});
app.use((err, req, res, next) => { // error-handling middleware function, It must have 4 parameters
res.status(500).send(err)
});
You can find document in here: https://expressjs.com/en/guide/error-handling.html
I have error with "ERR_TOO_MANY_REDIRECTS" in browser.
On linux server it looks like:
Error: Can't set headers after they are sent.
This is my app.js:
const express = require('express');
const bodyParser = require('body-parser');
const mysql = require('mysql');
const path = require('path');
const app = express();
const session = require('express-session');
const {getHomePage} = require('./routes/index');
const {getmain, addUserPage, addUser, deleteUser, editUser, editUserPage, addTemplates, getHistory} = require('./routes/user');
const port = 5000;
var auth = function(req, res, next) {
if (req.session && req.session.user === "amy" && req.session.admin)
return next();
else
return res.sendStatus(401);
};
// create connection to database
// the mysql.createConnection function takes in a configuration object which contains host, user, password and the database name.
const db = mysql.createConnection ({
host: 'localhost',
user: '*****',
password: '*****',
database: '*****',
charset : 'utf8mb4'
});
// connect to database
db.connect((err) => {
if (err) {
throw err;
}
console.log('Connected to database');
});
global.db = db;
// configure middleware
app.set('port', process.env.port || port); // set express to use this port
app.set('views', __dirname + '/views'); // set express to look in this folder to render our view
app.set('view engine', 'ejs'); // configure template engine
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json()); // parse form data client
app.use(express.static(path.join(__dirname, 'public'))); // configure express to use public folder
app.locals.moment = require('moment');
app.use(session({
secret: '2C44-4D44-WppQ38S',
resave: true,
saveUninitialized: true,
cookie: { maxAge: 1800000}
}));
// routes for the app
app.get('/', getHomePage);
app.get('/main', auth, getmain);
app.get('/add', auth, addUserPage);
app.get('/edit/:id', auth, editUserPage);
app.get('/delete/:id', auth, deleteUser);
app.get('/addtemp/:login', auth, addTemplates);
app.get('/history', auth, getHistory)
app.post('/add', auth, addUser);
app.post('/edit/:id', auth, editUser);
app.post('/addtemp/:login', auth, addTemplates);
// Login endpoint
app.get('/login', function (req, res) {
if (!req.query.username || !req.query.password) {
res.send('login failed');
} else if(req.query.username === "amy" || req.query.password === "amy") {
req.session.user = "amy";
req.session.admin = true;
res.redirect('/main');
}
});
// Logout endpoint
app.get('/logout', function (req, res) {
req.session.destroy();
res.redirect("/");
});
// set the app to listen on the port
app.listen(port, () => {
console.log(`Server running on port: ${port}`);
});
I know I have some problems in
app.get('login...
Theres everything ok with success login using correct username and password but when I use incorrect nothing happend.
This is my module getmain (after correct login):
getmain: (req, res) => {
let query = "SELECT * FROM `users` ORDER BY id ASC";
// execute query
db.query(query, (err, result) => {
if (err) {
res.redirect('/main');
}
res.render('main.ejs', {
title: "blablabla"
,users: result
});
});
},
And this is index.js:
module.exports = {
getHomePage: (req, res) => {
let query = "SELECT * FROM `users` ORDER BY id ASC";
// execute query
db.query(query, (err, result) => {
if (err) {
res.redirect('/');
}
res.render('index.ejs', {
title: "blablabla"
,users: result
});
});
},
};
I read that's all because by looping but I can not figure it out.
I will be grateful for directing me to the source of the problem.
I have this simple enough express application which tries to use Active Directory to authenticate the user. Here is my setup:
const express = require('express');
const session = require('express-session');
const passport = require('passport');
const ActiveDirectoryStrategy = require('passport-activedirectory');
const PORT = process.env.PORT || 8080;
// AD configuration. Real values omitted.
const config = {
url: 'ldaps://...',
baseDN: '...',
username: '...',
password: '...'
};
const app = express();
app.use(session({
secret: 'mysessionsecret',
resave: true,
saveUninitialized: true
}));
app.use(passport.initialize());
app.use(passport.session());
passport.use('ad', new ActiveDirectoryStrategy(
{
ldap: config
},
(profile, ad, done) => {
// The problem is here! This never gets called!
console.log('ActiveDirectoryStrategy activated.');
done('ActiveDirectoryStrategy not implemented.');
}
));
// route middleware to ensure user is logged in
const isLoggedIn = (req, res, next) =>
req.isAuthenticated() ? next() : res.redirect('/auth');
app.get('/', isLoggedIn, (req, res) => {
res.end('You are logged in.');
});
app.get('/unsecured', (req, res) => {
res.end('You are not logged in.');
});
app.get('/auth', passport.authenticate('ad', {
successRedirect: '/',
failureRedirect: '/unsecured'
}));
app.listen(PORT);
console.log('Listening on port ' + PORT);
However, the verifier function I pass for the constructor ActiveDirectoryStrategy never gets called. (This is the function with the signature (profile, ad, done)).
I am sure that there is no problem with the LDAP configuration, because I can access the active directory just fine with activedirectory module with the same parameters:
const ActiveDirectory = require('activedirectory');
const ad = new ActiveDirectory(config);
ad.findUser('username', (err, user) => {
if (err) {
return console.log(err);
}
console.log(JSON.stringify(user));
// prints an object with user's info
});
So there must be a problem with my routing. What am I doing wrong? Why is my verifier function not getting called?
The problem was a misconception I had. I thought the Passport.js's authenticate middleware would perform the NTLM handshake for me. This is not the case. passport-activedirectory actually needs something like IISNode to run in front of it in order to perform the NTLM handshake. Since the request does not contain the authentication information,
I settled on using express-ntlm middleware as a result. express-ntlm gives you the UserName, DomainName, and Workstation properties you can use. But if you want to acquire the full AD profile for some reason, you could setup a custom passport strategy like so:
const ActiveDirectory = require('activedirectory');
const CustomStrategy = require('passport-custom');
passport.use('ntlm-ad-backend', new CustomStrategy((req, done) => {
let username = req.ntlm.UserName;
AD.findUser(username, (err, profile) => {
if (err) {
console.log(err);
done(err);
}
if (!profile) {
done(new Error(`User ${req.ntlm.UserName} not found in Active Director.`));
} else {
done(null, profile);
}
});
}));
And then,
app.get('/auth', passport.authenticate('ntlm-ad-backend', {
successRedirect: '/',
failureRedirect: '/unsecured'
}));
Note that you will also have to implement serializeUser and deserializeUser.
I have a tremendous headche with a problem when I try to login using Passport.
I'm making a post request to /login with an email and password. Passport authenticates it correctly, err isn't called and then return res.redirect('/user') gets called too but it returns 404 and doesn't redirect.
I don't know why this is happening. Here's my code:
Server (index.js):
const
express = require('express'),
app = express(),
mongoose = require('mongoose'),
passport = require('passport'),
cookieSession = require('cookie-session'),
bodyParser = require('body-parser'),
keys = require('./config/keys'),
user = require('./models/User'),
passportService = require('./services/passport'),
authRoutes = require('./routes/auth');
mongoose.connect(keys.mongoURI);
app.use(bodyParser.json());
app.use(
cookieSession({
maxAge: 15 * 24 * 60 * 60 * 1000,
keys: [keys.cookieKey]
})
);
app.use(passport.initialize());
app.use(passport.session());
passportService(passport);
authRoutes(app);
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
console.log(`App listening on port ${PORT}`);
});
passportService (passport.js):
const LocalStrategy = require('passport-local').Strategy;
const mongoose = require('mongoose');
const User = mongoose.model('users');
module.exports = (passport) => {
passport.serializeUser((user, done) => {
done(null, user._id);
});
passport.deserializeUser((id, done) => {
User.findById(id).then(user => {
done(null, user);
});
});
passport.use(
new LocalStrategy(
{
usernameField: 'emailaddr',
passwordField: 'passwd'
},
function(username, password, done) {
User.findOne({ email: username }, function(err, user) {
if(err){
return done(err);
}
if(!user) {
console.log('User not found with email: ', username);
return done(null, false);
}
if(user.password != password) {
console.log('Invalid password');
return done(null, false)
}
return done(null, user);
});
}));
}
Authentication route:
const passport = require('passport');
module.exports = app => {
app.post('/api/login', function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err) { return next(err); }
if (!user) { return res.redirect('/login'); }
req.logIn(user, function(err) {
if (err) { return next(err); }
return res.redirect('/user');
});
})(req, res, next);
});
}
There is not route for /user because I'm working with React and React Router in the client.
Please I need your help!!!
I would suggest not using the custom callback on your authenticate function. Instead, check to see if after the api is called and authentication is run if you have a user attached to the request object.
// calling redirects from your api will be problematic with react router
// allow your front end to decide what route to call based on the response
app.post('/api/login', passport.authenticate('local'), function(req, res, next) {
if(req.user) {
res.json(req.user);
} else {
// handle errors here, decide what you want to send back to your front end
// so that it knows the user wasn't found
res.statusCode = 503;
res.send({message: 'Not Found'})
}
});